Import cobalt 25.master.0.1034729
diff --git a/base/threading/OWNERS b/base/threading/OWNERS
new file mode 100644
index 0000000..9c20e00
--- /dev/null
+++ b/base/threading/OWNERS
@@ -0,0 +1,2 @@
+# For HangWatcher implementation and tests:
+per-file hang_watcher*=olivierli@chromium.org
diff --git a/base/threading/counter_perftest.cc b/base/threading/counter_perftest.cc
new file mode 100644
index 0000000..db3b99d
--- /dev/null
+++ b/base/threading/counter_perftest.cc
@@ -0,0 +1,180 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <atomic>
+#include <string>
+
+#include "base/barrier_closure.h"
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/simple_thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+
+// This file contains tests to measure the cost of incrementing:
+// - A non-atomic variable, no lock.
+// - A non-atomic variable, with lock.
+// - An atomic variable, no memory barriers.
+// - An atomic variable, acquire-release barriers.
+// The goal is to provide data to guide counter implementation choices.
+
+namespace base {
+
+namespace {
+
+constexpr char kMetricPrefixCounter[] = "Counter.";
+constexpr char kMetricOperationThroughput[] = "operation_throughput";
+constexpr uint64_t kNumIterations = 100000000;
+
+perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixCounter, story_name);
+ reporter.RegisterImportantMetric(kMetricOperationThroughput, "operations/ms");
+ return reporter;
+}
+
+class Uint64_NoLock {
+ public:
+ Uint64_NoLock() = default;
+ void Increment() { counter_ = counter_ + 1; }
+ uint64_t value() const { return counter_; }
+
+ private:
+ // Volatile to prevent the compiler from over-optimizing the increment.
+ volatile uint64_t counter_ = 0;
+};
+
+class Uint64_Lock {
+ public:
+ Uint64_Lock() = default;
+ void Increment() {
+ AutoLock auto_lock(lock_);
+ ++counter_;
+ }
+ uint64_t value() const {
+ AutoLock auto_lock(lock_);
+ return counter_;
+ }
+
+ private:
+ mutable Lock lock_;
+ uint64_t counter_ GUARDED_BY(lock_) = 0;
+};
+
+class AtomicUint64_NoBarrier {
+ public:
+ AtomicUint64_NoBarrier() = default;
+ void Increment() { counter_.fetch_add(1, std::memory_order_relaxed); }
+ uint64_t value() const { return counter_; }
+
+ private:
+ std::atomic<uint64_t> counter_{0};
+};
+
+class AtomicUint64_Barrier {
+ public:
+ AtomicUint64_Barrier() = default;
+ void Increment() { counter_.fetch_add(1, std::memory_order_acq_rel); }
+ uint64_t value() const { return counter_; }
+
+ private:
+ std::atomic<uint64_t> counter_{0};
+};
+
+template <typename CounterType>
+class IncrementThread : public SimpleThread {
+ public:
+ // Upon entering its main function, the thread waits for |start_event| to be
+ // signaled. Then, it increments |counter| |kNumIterations| times.
+ // Finally, it invokes |done_closure|.
+ explicit IncrementThread(WaitableEvent* start_event,
+ CounterType* counter,
+ OnceClosure done_closure)
+ : SimpleThread("IncrementThread"),
+ start_event_(start_event),
+ counter_(counter),
+ done_closure_(std::move(done_closure)) {}
+
+ // SimpleThread:
+ void Run() override {
+ start_event_->Wait();
+ for (uint64_t i = 0; i < kNumIterations; ++i)
+ counter_->Increment();
+ std::move(done_closure_).Run();
+ }
+
+ private:
+ const raw_ptr<WaitableEvent> start_event_;
+ const raw_ptr<CounterType> counter_;
+ OnceClosure done_closure_;
+};
+
+template <typename CounterType>
+void RunIncrementPerfTest(const std::string& story_name, int num_threads) {
+ WaitableEvent start_event;
+ WaitableEvent end_event;
+ CounterType counter;
+ RepeatingClosure done_closure = BarrierClosure(
+ num_threads, BindOnce(&WaitableEvent::Signal, Unretained(&end_event)));
+
+ std::vector<std::unique_ptr<IncrementThread<CounterType>>> threads;
+ for (int i = 0; i < num_threads; ++i) {
+ threads.push_back(std::make_unique<IncrementThread<CounterType>>(
+ &start_event, &counter, done_closure));
+ threads.back()->Start();
+ }
+
+ TimeTicks start_time = TimeTicks::Now();
+ start_event.Signal();
+ end_event.Wait();
+ TimeTicks end_time = TimeTicks::Now();
+
+ EXPECT_EQ(num_threads * kNumIterations, counter.value());
+
+ auto reporter = SetUpReporter(story_name);
+ reporter.AddResult(
+ kMetricOperationThroughput,
+ kNumIterations / (end_time - start_time).InMillisecondsF());
+
+ for (auto& thread : threads)
+ thread->Join();
+}
+
+} // namespace
+
+TEST(CounterPerfTest, Uint64_NoLock_1Thread) {
+ RunIncrementPerfTest<Uint64_NoLock>("Uint64_NoLock_1Thread", 1);
+}
+
+// No Uint64_NoLock_4Threads test because it would cause data races.
+
+TEST(CounterPerfTest, Uint64_Lock_1Thread) {
+ RunIncrementPerfTest<Uint64_Lock>("Uint64_Lock_1Thread", 1);
+}
+
+TEST(CounterPerfTest, Uint64_Lock_4Threads) {
+ RunIncrementPerfTest<Uint64_Lock>("Uint64_Lock_4Threads", 4);
+}
+
+TEST(CounterPerfTest, AtomicUint64_NoBarrier_1Thread) {
+ RunIncrementPerfTest<AtomicUint64_NoBarrier>("AtomicUint64_NoBarrier_1Thread",
+ 1);
+}
+
+TEST(CounterPerfTest, AtomicUint64_NoBarrier_4Threads) {
+ RunIncrementPerfTest<AtomicUint64_NoBarrier>(
+ "AtomicUint64_NoBarrier_4Threads", 4);
+}
+
+TEST(CounterPerfTest, AtomicUint64_Barrier_1Thread) {
+ RunIncrementPerfTest<AtomicUint64_Barrier>("AtomicUint64_Barrier_1Thread", 1);
+}
+
+TEST(CounterPerfTest, AtomicUint64_Barrier_4Threads) {
+ RunIncrementPerfTest<AtomicUint64_Barrier>("AtomicUint64_Barrier_4Threads",
+ 4);
+}
+
+} // namespace base
diff --git a/base/threading/hang_watcher.cc b/base/threading/hang_watcher.cc
new file mode 100644
index 0000000..db5d561
--- /dev/null
+++ b/base/threading/hang_watcher.cc
@@ -0,0 +1,1328 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/hang_watcher.h"
+
+#include <atomic>
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/debug/alias.h"
+#include "base/debug/crash_logging.h"
+#include "base/debug/dump_without_crashing.h"
+#include "base/debug/leak_annotations.h"
+#include "base/feature_list.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/power_monitor/power_monitor.h"
+#include "base/ranges/algorithm.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/threading_features.h"
+#include "base/time/default_tick_clock.h"
+#include "base/time/time.h"
+#include "base/trace_event/base_tracing.h"
+#include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/base/attributes.h"
+
+#if defined(STARBOARD)
+#include <pthread.h>
+
+#include "base/check_op.h"
+#include "starboard/thread.h"
+#endif
+
+namespace base {
+
+namespace {
+
+// Defines how much logging happens when the HangWatcher monitors the threads.
+// Logging levels are set per thread type through Finch. It's important that
+// the order of the enum members stay the same and that their numerical
+// values be in increasing order. The implementation of
+// ThreadTypeLoggingLevelGreaterOrEqual() depends on it.
+enum class LoggingLevel { kNone = 0, kUmaOnly = 1, kUmaAndCrash = 2 };
+
+HangWatcher* g_instance = nullptr;
+
+#if defined(STARBOARD)
+ABSL_CONST_INIT pthread_once_t s_once_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_key = 0;
+
+void InitThreadLocalKey() {
+ int res = pthread_key_create(&s_thread_local_key , NULL);
+ DCHECK(res == 0);
+}
+
+void EnsureThreadLocalKeyInited() {
+ pthread_once(&s_once_flag, InitThreadLocalKey);
+}
+
+internal::HangWatchState* GetHangWatchState() {
+ EnsureThreadLocalKeyInited();
+ return static_cast<internal::HangWatchState*>(
+ pthread_getspecific(s_thread_local_key));
+}
+#else
+ABSL_CONST_INIT thread_local internal::HangWatchState* hang_watch_state =
+ nullptr;
+#endif
+
+std::atomic<bool> g_use_hang_watcher{false};
+std::atomic<HangWatcher::ProcessType> g_hang_watcher_process_type{
+ HangWatcher::ProcessType::kBrowserProcess};
+
+std::atomic<LoggingLevel> g_threadpool_log_level{LoggingLevel::kNone};
+std::atomic<LoggingLevel> g_io_thread_log_level{LoggingLevel::kNone};
+std::atomic<LoggingLevel> g_main_thread_log_level{LoggingLevel::kNone};
+
+// Indicates whether HangWatcher::Run() should return after the next monitoring.
+std::atomic<bool> g_keep_monitoring{true};
+
+// Emits the hung thread count histogram. |count| is the number of threads
+// of type |thread_type| that were hung or became hung during the last
+// monitoring window. This function should be invoked for each thread type
+// encountered on each call to Monitor().
+void LogHungThreadCountHistogram(HangWatcher::ThreadType thread_type,
+ int count) {
+ // In the case of unique threads like the IO or UI/Main thread a count does
+ // not make sense.
+ const bool any_thread_hung = count >= 1;
+
+ const HangWatcher::ProcessType process_type =
+ g_hang_watcher_process_type.load(std::memory_order_relaxed);
+ switch (process_type) {
+ case HangWatcher::ProcessType::kUnknownProcess:
+ break;
+
+ case HangWatcher::ProcessType::kBrowserProcess:
+ switch (thread_type) {
+ case HangWatcher::ThreadType::kIOThread:
+ UMA_HISTOGRAM_BOOLEAN(
+ "HangWatcher.IsThreadHung.BrowserProcess."
+ "IOThread",
+ any_thread_hung);
+ break;
+ case HangWatcher::ThreadType::kMainThread:
+ UMA_HISTOGRAM_BOOLEAN(
+ "HangWatcher.IsThreadHung.BrowserProcess."
+ "UIThread",
+ any_thread_hung);
+ break;
+ case HangWatcher::ThreadType::kThreadPoolThread:
+ // Not recorded for now.
+ break;
+ }
+ break;
+
+ case HangWatcher::ProcessType::kGPUProcess:
+ // Not recorded for now.
+ break;
+
+ case HangWatcher::ProcessType::kRendererProcess:
+ switch (thread_type) {
+ case HangWatcher::ThreadType::kIOThread:
+ UMA_HISTOGRAM_BOOLEAN(
+ "HangWatcher.IsThreadHung.RendererProcess."
+ "IOThread",
+ any_thread_hung);
+ break;
+ case HangWatcher::ThreadType::kMainThread:
+ UMA_HISTOGRAM_BOOLEAN(
+ "HangWatcher.IsThreadHung.RendererProcess."
+ "MainThread",
+ any_thread_hung);
+ break;
+ case HangWatcher::ThreadType::kThreadPoolThread:
+ // Not recorded for now.
+ break;
+ }
+ break;
+
+ case HangWatcher::ProcessType::kUtilityProcess:
+ switch (thread_type) {
+ case HangWatcher::ThreadType::kIOThread:
+ UMA_HISTOGRAM_BOOLEAN(
+ "HangWatcher.IsThreadHung.UtilityProcess."
+ "IOThread",
+ any_thread_hung);
+ break;
+ case HangWatcher::ThreadType::kMainThread:
+ UMA_HISTOGRAM_BOOLEAN(
+ "HangWatcher.IsThreadHung.UtilityProcess."
+ "MainThread",
+ any_thread_hung);
+ break;
+ case HangWatcher::ThreadType::kThreadPoolThread:
+ // Not recorded for now.
+ break;
+ }
+ break;
+ }
+}
+
+// Returns true if |thread_type| was configured through Finch to have a logging
+// level that is equal to or exceeds |logging_level|.
+bool ThreadTypeLoggingLevelGreaterOrEqual(HangWatcher::ThreadType thread_type,
+ LoggingLevel logging_level) {
+ switch (thread_type) {
+ case HangWatcher::ThreadType::kIOThread:
+ return g_io_thread_log_level.load(std::memory_order_relaxed) >=
+ logging_level;
+ case HangWatcher::ThreadType::kMainThread:
+ return g_main_thread_log_level.load(std::memory_order_relaxed) >=
+ logging_level;
+ case HangWatcher::ThreadType::kThreadPoolThread:
+ return g_threadpool_log_level.load(std::memory_order_relaxed) >=
+ logging_level;
+ }
+}
+
+} // namespace
+
+// Determines if the HangWatcher is activated. When false the HangWatcher
+// thread never started.
+BASE_FEATURE(kEnableHangWatcher,
+ "EnableHangWatcher",
+ FEATURE_ENABLED_BY_DEFAULT);
+
+// Browser process.
+constexpr base::FeatureParam<int> kIOThreadLogLevel{
+ &kEnableHangWatcher, "io_thread_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+constexpr base::FeatureParam<int> kUIThreadLogLevel{
+ &kEnableHangWatcher, "ui_thread_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+constexpr base::FeatureParam<int> kThreadPoolLogLevel{
+ &kEnableHangWatcher, "threadpool_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+
+// GPU process.
+constexpr base::FeatureParam<int> kGPUProcessIOThreadLogLevel{
+ &kEnableHangWatcher, "gpu_process_io_thread_log_level",
+ static_cast<int>(LoggingLevel::kNone)};
+constexpr base::FeatureParam<int> kGPUProcessMainThreadLogLevel{
+ &kEnableHangWatcher, "gpu_process_main_thread_log_level",
+ static_cast<int>(LoggingLevel::kNone)};
+constexpr base::FeatureParam<int> kGPUProcessThreadPoolLogLevel{
+ &kEnableHangWatcher, "gpu_process_threadpool_log_level",
+ static_cast<int>(LoggingLevel::kNone)};
+
+// Renderer process.
+constexpr base::FeatureParam<int> kRendererProcessIOThreadLogLevel{
+ &kEnableHangWatcher, "renderer_process_io_thread_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+constexpr base::FeatureParam<int> kRendererProcessMainThreadLogLevel{
+ &kEnableHangWatcher, "renderer_process_main_thread_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+constexpr base::FeatureParam<int> kRendererProcessThreadPoolLogLevel{
+ &kEnableHangWatcher, "renderer_process_threadpool_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+
+// Utility process.
+constexpr base::FeatureParam<int> kUtilityProcessIOThreadLogLevel{
+ &kEnableHangWatcher, "utility_process_io_thread_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+constexpr base::FeatureParam<int> kUtilityProcessMainThreadLogLevel{
+ &kEnableHangWatcher, "utility_process_main_thread_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+constexpr base::FeatureParam<int> kUtilityProcessThreadPoolLogLevel{
+ &kEnableHangWatcher, "utility_process_threadpool_log_level",
+ static_cast<int>(LoggingLevel::kUmaOnly)};
+
+constexpr const char* kThreadName = "HangWatcher";
+
+// The time that the HangWatcher thread will sleep for between calls to
+// Monitor(). Increasing or decreasing this does not modify the type of hangs
+// that can be detected. It instead increases the probability that a call to
+// Monitor() will happen at the right time to catch a hang. This has to be
+// balanced with power/cpu use concerns as busy looping would catch amost all
+// hangs but present unacceptable overhead. NOTE: If this period is ever changed
+// then all metrics that depend on it like
+// HangWatcher.IsThreadHung need to be updated.
+constexpr auto kMonitoringPeriod = base::Seconds(10);
+
+WatchHangsInScope::WatchHangsInScope(TimeDelta timeout) {
+ internal::HangWatchState* current_hang_watch_state =
+ HangWatcher::IsEnabled()
+ ? internal::HangWatchState::GetHangWatchStateForCurrentThread()
+ : nullptr;
+
+ DCHECK(timeout >= base::TimeDelta()) << "Negative timeouts are invalid.";
+
+ // Thread is not monitored, noop.
+ if (!current_hang_watch_state) {
+ took_effect_ = false;
+ return;
+ }
+
+#if DCHECK_IS_ON()
+ previous_watch_hangs_in_scope_ =
+ current_hang_watch_state->GetCurrentWatchHangsInScope();
+ current_hang_watch_state->SetCurrentWatchHangsInScope(this);
+#endif
+
+ auto [old_flags, old_deadline] =
+ current_hang_watch_state->GetFlagsAndDeadline();
+
+ // TODO(crbug.com/1034046): Check whether we are over deadline already for the
+ // previous WatchHangsInScope here by issuing only one TimeTicks::Now()
+ // and resuing the value.
+
+ previous_deadline_ = old_deadline;
+ TimeTicks deadline = TimeTicks::Now() + timeout;
+ current_hang_watch_state->SetDeadline(deadline);
+ current_hang_watch_state->IncrementNestingLevel();
+
+ const bool hangs_ignored_for_current_scope =
+ internal::HangWatchDeadline::IsFlagSet(
+ internal::HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope,
+ old_flags);
+
+ // If the current WatchHangsInScope is ignored, temporarily reactivate hang
+ // watching for newly created WatchHangsInScopes. On exiting hang watching
+ // is suspended again to return to the original state.
+ if (hangs_ignored_for_current_scope) {
+ current_hang_watch_state->UnsetIgnoreCurrentWatchHangsInScope();
+ set_hangs_ignored_on_exit_ = true;
+ }
+}
+
+WatchHangsInScope::~WatchHangsInScope() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ // If hang watching was not enabled at construction time there is nothing to
+ // validate or undo.
+ if (!took_effect_) {
+ return;
+ }
+
+ // If the thread was unregistered since construction there is also nothing to
+ // do.
+ auto* const state =
+ internal::HangWatchState::GetHangWatchStateForCurrentThread();
+ if (!state) {
+ return;
+ }
+
+ // If a hang is currently being captured we should block here so execution
+ // stops and we avoid recording unrelated stack frames in the crash.
+ if (state->IsFlagSet(internal::HangWatchDeadline::Flag::kShouldBlockOnHang)) {
+ base::HangWatcher::GetInstance()->BlockIfCaptureInProgress();
+ }
+
+#if DCHECK_IS_ON()
+ // Verify that no Scope was destructed out of order.
+ DCHECK_EQ(this, state->GetCurrentWatchHangsInScope());
+ state->SetCurrentWatchHangsInScope(previous_watch_hangs_in_scope_);
+#endif
+
+ if (state->nesting_level() == 1) {
+ // If a call to InvalidateActiveExpectations() suspended hang watching
+ // during the lifetime of this or any nested WatchHangsInScope it can now
+ // safely be reactivated by clearing the ignore bit since this is the
+ // outer-most scope.
+ state->UnsetIgnoreCurrentWatchHangsInScope();
+ } else if (set_hangs_ignored_on_exit_) {
+ // Return to ignoring hangs since this was the previous state before hang
+ // watching was temporarily enabled for this WatchHangsInScope only in the
+ // constructor.
+ state->SetIgnoreCurrentWatchHangsInScope();
+ }
+
+ // Reset the deadline to the value it had before entering this
+ // WatchHangsInScope.
+ state->SetDeadline(previous_deadline_);
+ // TODO(crbug.com/1034046): Log when a WatchHangsInScope exits after its
+ // deadline and that went undetected by the HangWatcher.
+
+ state->DecrementNestingLevel();
+}
+
+// static
+void HangWatcher::InitializeOnMainThread(ProcessType process_type) {
+ DCHECK(!g_use_hang_watcher);
+ DCHECK(g_io_thread_log_level == LoggingLevel::kNone);
+ DCHECK(g_main_thread_log_level == LoggingLevel::kNone);
+ DCHECK(g_threadpool_log_level == LoggingLevel::kNone);
+
+ bool enable_hang_watcher = base::FeatureList::IsEnabled(kEnableHangWatcher);
+
+ // Do not start HangWatcher in the GPU process until the issue related to
+ // invalid magic signature in the GPU WatchDog is fixed
+ // (https://crbug.com/1297760).
+ if (process_type == ProcessType::kGPUProcess)
+ enable_hang_watcher = false;
+
+ g_use_hang_watcher.store(enable_hang_watcher, std::memory_order_relaxed);
+
+ // Keep the process type.
+ g_hang_watcher_process_type.store(process_type, std::memory_order_relaxed);
+
+ // If hang watching is disabled as a whole there is no need to read the
+ // params.
+ if (!enable_hang_watcher)
+ return;
+
+ // Retrieve thread-specific config for hang watching.
+ switch (process_type) {
+ case HangWatcher::ProcessType::kUnknownProcess:
+ break;
+
+ case HangWatcher::ProcessType::kBrowserProcess:
+ g_threadpool_log_level.store(
+ static_cast<LoggingLevel>(kThreadPoolLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_io_thread_log_level.store(
+ static_cast<LoggingLevel>(kIOThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_main_thread_log_level.store(
+ static_cast<LoggingLevel>(kUIThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ break;
+
+ case HangWatcher::ProcessType::kGPUProcess:
+ g_threadpool_log_level.store(
+ static_cast<LoggingLevel>(kGPUProcessThreadPoolLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_io_thread_log_level.store(
+ static_cast<LoggingLevel>(kGPUProcessIOThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_main_thread_log_level.store(
+ static_cast<LoggingLevel>(kGPUProcessMainThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ break;
+
+ case HangWatcher::ProcessType::kRendererProcess:
+ g_threadpool_log_level.store(
+ static_cast<LoggingLevel>(kRendererProcessThreadPoolLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_io_thread_log_level.store(
+ static_cast<LoggingLevel>(kRendererProcessIOThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_main_thread_log_level.store(
+ static_cast<LoggingLevel>(kRendererProcessMainThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ break;
+
+ case HangWatcher::ProcessType::kUtilityProcess:
+ g_threadpool_log_level.store(
+ static_cast<LoggingLevel>(kUtilityProcessThreadPoolLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_io_thread_log_level.store(
+ static_cast<LoggingLevel>(kUtilityProcessIOThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ g_main_thread_log_level.store(
+ static_cast<LoggingLevel>(kUtilityProcessMainThreadLogLevel.Get()),
+ std::memory_order_relaxed);
+ break;
+ }
+}
+
+void HangWatcher::UnitializeOnMainThreadForTesting() {
+ g_use_hang_watcher.store(false, std::memory_order_relaxed);
+ g_threadpool_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
+ g_io_thread_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
+ g_main_thread_log_level.store(LoggingLevel::kNone, std::memory_order_relaxed);
+}
+
+// static
+bool HangWatcher::IsEnabled() {
+ return g_use_hang_watcher.load(std::memory_order_relaxed);
+}
+
+// static
+bool HangWatcher::IsThreadPoolHangWatchingEnabled() {
+ return g_threadpool_log_level.load(std::memory_order_relaxed) !=
+ LoggingLevel::kNone;
+}
+
+// static
+bool HangWatcher::IsIOThreadHangWatchingEnabled() {
+ return g_io_thread_log_level.load(std::memory_order_relaxed) !=
+ LoggingLevel::kNone;
+}
+
+// static
+bool HangWatcher::IsCrashReportingEnabled() {
+ if (g_main_thread_log_level.load(std::memory_order_relaxed) ==
+ LoggingLevel::kUmaAndCrash) {
+ return true;
+ }
+ if (g_io_thread_log_level.load(std::memory_order_relaxed) ==
+ LoggingLevel::kUmaAndCrash) {
+ return true;
+ }
+ if (g_threadpool_log_level.load(std::memory_order_relaxed) ==
+ LoggingLevel::kUmaAndCrash) {
+ return true;
+ }
+ return false;
+}
+
+// static
+void HangWatcher::InvalidateActiveExpectations() {
+ auto* const state =
+ internal::HangWatchState::GetHangWatchStateForCurrentThread();
+ if (!state) {
+ // If the current thread is not under watch there is nothing to invalidate.
+ return;
+ }
+ state->SetIgnoreCurrentWatchHangsInScope();
+}
+
+HangWatcher::HangWatcher()
+ : monitor_period_(kMonitoringPeriod),
+ should_monitor_(WaitableEvent::ResetPolicy::AUTOMATIC),
+ thread_(this, kThreadName),
+ tick_clock_(base::DefaultTickClock::GetInstance()),
+ memory_pressure_listener_(
+ FROM_HERE,
+ base::BindRepeating(&HangWatcher::OnMemoryPressure,
+ base::Unretained(this))) {
+ // |thread_checker_| should not be bound to the constructing thread.
+ DETACH_FROM_THREAD(hang_watcher_thread_checker_);
+
+ should_monitor_.declare_only_used_while_idle();
+
+ DCHECK(!g_instance);
+ g_instance = this;
+}
+
+// static
+void HangWatcher::CreateHangWatcherInstance() {
+ DCHECK(!g_instance);
+ g_instance = new base::HangWatcher();
+ // The hang watcher is leaked to make sure it survives all watched threads.
+ ANNOTATE_LEAKING_OBJECT_PTR(g_instance);
+}
+
+#if !BUILDFLAG(IS_NACL)
+debug::ScopedCrashKeyString
+HangWatcher::GetTimeSinceLastCriticalMemoryPressureCrashKey() {
+ DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
+
+ // The crash key size is large enough to hold the biggest possible return
+ // value from base::TimeDelta::InSeconds().
+ constexpr debug::CrashKeySize kCrashKeyContentSize =
+ debug::CrashKeySize::Size32;
+ DCHECK_GE(static_cast<uint64_t>(kCrashKeyContentSize),
+ base::NumberToString(std::numeric_limits<int64_t>::max()).size());
+
+ static debug::CrashKeyString* crash_key = AllocateCrashKeyString(
+ "seconds-since-last-memory-pressure", kCrashKeyContentSize);
+
+ const base::TimeTicks last_critical_memory_pressure_time =
+ last_critical_memory_pressure_.load(std::memory_order_relaxed);
+ if (last_critical_memory_pressure_time.is_null()) {
+ constexpr char kNoMemoryPressureMsg[] = "No critical memory pressure";
+ static_assert(
+ std::size(kNoMemoryPressureMsg) <=
+ static_cast<uint64_t>(kCrashKeyContentSize),
+ "The crash key is too small to hold \"No critical memory pressure\".");
+ return debug::ScopedCrashKeyString(crash_key, kNoMemoryPressureMsg);
+ } else {
+ base::TimeDelta time_since_last_critical_memory_pressure =
+ base::TimeTicks::Now() - last_critical_memory_pressure_time;
+ return debug::ScopedCrashKeyString(
+ crash_key, base::NumberToString(
+ time_since_last_critical_memory_pressure.InSeconds()));
+ }
+}
+#endif
+
+std::string HangWatcher::GetTimeSinceLastSystemPowerResumeCrashKeyValue()
+ const {
+ DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
+
+ const TimeTicks last_system_power_resume_time =
+ PowerMonitor::GetLastSystemResumeTime();
+ if (last_system_power_resume_time.is_null())
+ return "Never suspended";
+ if (last_system_power_resume_time == TimeTicks::Max())
+ return "Power suspended";
+
+ const TimeDelta time_since_last_system_resume =
+ TimeTicks::Now() - last_system_power_resume_time;
+ return NumberToString(time_since_last_system_resume.InSeconds());
+}
+
+void HangWatcher::OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
+ if (memory_pressure_level ==
+ base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
+ last_critical_memory_pressure_.store(base::TimeTicks::Now(),
+ std::memory_order_relaxed);
+ }
+}
+
+HangWatcher::~HangWatcher() {
+ DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
+ DCHECK_EQ(g_instance, this);
+ DCHECK(watch_states_.empty());
+ g_instance = nullptr;
+ Stop();
+}
+
+void HangWatcher::Start() {
+ thread_.Start();
+}
+
+void HangWatcher::Stop() {
+ g_keep_monitoring.store(false, std::memory_order_relaxed);
+ should_monitor_.Signal();
+ thread_.Join();
+
+ // In production HangWatcher is always leaked but during testing it's possibly
+ // stopped and restarted using a new instance. This makes sure the next call
+ // to Start() will actually monitor in that case.
+ g_keep_monitoring.store(true, std::memory_order_relaxed);
+}
+
+bool HangWatcher::IsWatchListEmpty() {
+ AutoLock auto_lock(watch_state_lock_);
+ return watch_states_.empty();
+}
+
+void HangWatcher::Wait() {
+ while (true) {
+ // Amount by which the actual time spent sleeping can deviate from
+ // the target time and still be considered timely.
+ constexpr base::TimeDelta kWaitDriftTolerance = base::Milliseconds(100);
+
+ const base::TimeTicks time_before_wait = tick_clock_->NowTicks();
+
+ // Sleep until next scheduled monitoring or until signaled.
+ const bool was_signaled = should_monitor_.TimedWait(monitor_period_);
+
+ if (after_wait_callback_)
+ after_wait_callback_.Run(time_before_wait);
+
+ const base::TimeTicks time_after_wait = tick_clock_->NowTicks();
+ const base::TimeDelta wait_time = time_after_wait - time_before_wait;
+ const bool wait_was_normal =
+ wait_time <= (monitor_period_ + kWaitDriftTolerance);
+
+ UMA_HISTOGRAM_TIMES("HangWatcher.SleepDrift.BrowserProcess",
+ wait_time - monitor_period_);
+
+ if (!wait_was_normal) {
+ // If the time spent waiting was too high it might indicate the machine is
+ // very slow or that that it went to sleep. In any case we can't trust the
+ // WatchHangsInScopes that are currently live. Update the ignore
+ // threshold to make sure they don't trigger a hang on subsequent monitors
+ // then keep waiting.
+
+ base::AutoLock auto_lock(watch_state_lock_);
+
+ // Find the latest deadline among the live watch states. They might change
+ // atomically while iterating but that's fine because if they do that
+ // means the new WatchHangsInScope was constructed very soon after the
+ // abnormal sleep happened and might be affected by the root cause still.
+ // Ignoring it is cautious and harmless.
+ base::TimeTicks latest_deadline;
+ for (const auto& state : watch_states_) {
+ base::TimeTicks deadline = state->GetDeadline();
+ if (deadline > latest_deadline) {
+ latest_deadline = deadline;
+ }
+ }
+
+ deadline_ignore_threshold_ = latest_deadline;
+ }
+
+ // Stop waiting.
+ if (wait_was_normal || was_signaled)
+ return;
+ }
+}
+
+void HangWatcher::Run() {
+ // Monitor() should only run on |thread_|. Bind |thread_checker_| here to make
+ // sure of that.
+ DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
+
+ while (g_keep_monitoring.load(std::memory_order_relaxed)) {
+ Wait();
+
+ if (!IsWatchListEmpty() &&
+ g_keep_monitoring.load(std::memory_order_relaxed)) {
+ Monitor();
+ if (after_monitor_closure_for_testing_) {
+ after_monitor_closure_for_testing_.Run();
+ }
+ }
+ }
+}
+
+// static
+HangWatcher* HangWatcher::GetInstance() {
+ return g_instance;
+}
+
+// static
+void HangWatcher::RecordHang() {
+ base::debug::DumpWithoutCrashing();
+ NO_CODE_FOLDING();
+}
+
+ScopedClosureRunner HangWatcher::RegisterThreadInternal(
+ ThreadType thread_type) {
+ AutoLock auto_lock(watch_state_lock_);
+ CHECK(base::FeatureList::GetInstance());
+
+ // Do not install a WatchState if the results would never be observable.
+ if (!ThreadTypeLoggingLevelGreaterOrEqual(thread_type,
+ LoggingLevel::kUmaOnly)) {
+ return ScopedClosureRunner(base::DoNothing());
+ }
+
+ watch_states_.push_back(
+ internal::HangWatchState::CreateHangWatchStateForCurrentThread(
+ thread_type));
+ return ScopedClosureRunner(BindOnce(&HangWatcher::UnregisterThread,
+ Unretained(HangWatcher::GetInstance())));
+}
+
+// static
+ScopedClosureRunner HangWatcher::RegisterThread(ThreadType thread_type) {
+ if (!GetInstance()) {
+ return ScopedClosureRunner();
+ }
+
+ return GetInstance()->RegisterThreadInternal(thread_type);
+}
+
+base::TimeTicks HangWatcher::WatchStateSnapShot::GetHighestDeadline() const {
+ DCHECK(IsActionable());
+
+ // Since entries are sorted in increasing order the last entry is the largest
+ // one.
+ return hung_watch_state_copies_.back().deadline;
+}
+
+HangWatcher::WatchStateSnapShot::WatchStateSnapShot() = default;
+
+void HangWatcher::WatchStateSnapShot::Init(
+ const HangWatchStates& watch_states,
+ base::TimeTicks deadline_ignore_threshold) {
+ DCHECK(!initialized_);
+
+ // No matter if the snapshot is actionable or not after this function
+ // it will have been initialized.
+ initialized_ = true;
+
+ const base::TimeTicks now = base::TimeTicks::Now();
+ bool all_threads_marked = true;
+ bool found_deadline_before_ignore_threshold = false;
+
+ // Use an std::array to store the hang counts to avoid allocations. The
+ // numerical values of the HangWatcher::ThreadType enum is used to index into
+ // the array. A |kInvalidHangCount| is used to signify there were no threads
+ // of the type found.
+ constexpr size_t kHangCountArraySize =
+ static_cast<std::size_t>(base::HangWatcher::ThreadType::kMax) + 1;
+ std::array<int, kHangCountArraySize> hung_counts_per_thread_type;
+
+ constexpr int kInvalidHangCount = -1;
+ hung_counts_per_thread_type.fill(kInvalidHangCount);
+
+ // Will be true if any of the hung threads has a logging level high enough,
+ // as defined through finch params, to warant dumping a crash.
+ bool any_hung_thread_has_dumping_enabled = false;
+
+ // Copy hung thread information.
+ for (const auto& watch_state : watch_states) {
+ uint64_t flags;
+ TimeTicks deadline;
+ std::tie(flags, deadline) = watch_state->GetFlagsAndDeadline();
+
+ if (deadline <= deadline_ignore_threshold) {
+ found_deadline_before_ignore_threshold = true;
+ }
+
+ if (internal::HangWatchDeadline::IsFlagSet(
+ internal::HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope,
+ flags)) {
+ continue;
+ }
+
+ // If a thread type is monitored and did not hang it still needs to be
+ // logged as a zero count;
+ const size_t hang_count_index =
+ static_cast<size_t>(watch_state.get()->thread_type());
+ if (hung_counts_per_thread_type[hang_count_index] == kInvalidHangCount) {
+ hung_counts_per_thread_type[hang_count_index] = 0;
+ }
+
+ // Only copy hung threads.
+ if (deadline <= now) {
+ ++hung_counts_per_thread_type[hang_count_index];
+
+ if (ThreadTypeLoggingLevelGreaterOrEqual(watch_state.get()->thread_type(),
+ LoggingLevel::kUmaAndCrash)) {
+ any_hung_thread_has_dumping_enabled = true;
+ }
+
+#if BUILDFLAG(ENABLE_BASE_TRACING)
+ // Emit trace events for monitored threads.
+ if (ThreadTypeLoggingLevelGreaterOrEqual(watch_state.get()->thread_type(),
+ LoggingLevel::kUmaOnly)) {
+ const PlatformThreadId thread_id = watch_state.get()->GetThreadID();
+ const auto track = perfetto::Track::FromPointer(
+ this, perfetto::ThreadTrack::ForThread(thread_id));
+ TRACE_EVENT_BEGIN("base", "HangWatcher::ThreadHung", track, deadline);
+ TRACE_EVENT_END("base", track, now);
+ // TODO(crbug.com/1021571): Remove this once fixed.
+ PERFETTO_INTERNAL_ADD_EMPTY_EVENT();
+ }
+#endif
+
+ // Attempt to mark the thread as needing to stay within its current
+ // WatchHangsInScope until capture is complete.
+ bool thread_marked = watch_state->SetShouldBlockOnHang(flags, deadline);
+
+ // If marking some threads already failed the snapshot won't be kept so
+ // there is no need to keep adding to it. The loop doesn't abort though
+ // to keep marking the other threads. If these threads remain hung until
+ // the next capture then they'll already be marked and will be included
+ // in the capture at that time.
+ if (thread_marked && all_threads_marked) {
+ hung_watch_state_copies_.push_back(
+ WatchStateCopy{deadline, watch_state.get()->GetThreadID()});
+ } else {
+ all_threads_marked = false;
+ }
+ }
+ }
+
+ // Log the hung thread counts to histograms for each thread type if any thread
+ // of the type were found.
+ for (size_t i = 0; i < kHangCountArraySize; ++i) {
+ const int hang_count = hung_counts_per_thread_type[i];
+ const HangWatcher::ThreadType thread_type =
+ static_cast<HangWatcher::ThreadType>(i);
+ if (hang_count != kInvalidHangCount &&
+ ThreadTypeLoggingLevelGreaterOrEqual(thread_type,
+ LoggingLevel::kUmaOnly)) {
+ LogHungThreadCountHistogram(thread_type, hang_count);
+ }
+ }
+
+ // Three cases can invalidate this snapshot and prevent the capture of the
+ // hang.
+ //
+ // 1. Some threads could not be marked for blocking so this snapshot isn't
+ // actionable since marked threads could be hung because of unmarked ones.
+ // If only the marked threads were captured the information would be
+ // incomplete.
+ //
+ // 2. Any of the threads have a deadline before |deadline_ignore_threshold|.
+ // If any thread is ignored it reduces the confidence in the whole state and
+ // it's better to avoid capturing misleading data.
+ //
+ // 3. The hung threads found were all of types that are not configured through
+ // Finch to trigger a crash dump.
+ //
+ if (!all_threads_marked || found_deadline_before_ignore_threshold ||
+ !any_hung_thread_has_dumping_enabled) {
+ hung_watch_state_copies_.clear();
+ return;
+ }
+
+ // Sort |hung_watch_state_copies_| by order of decreasing hang severity so the
+ // most severe hang is first in the list.
+ ranges::sort(hung_watch_state_copies_,
+ [](const WatchStateCopy& lhs, const WatchStateCopy& rhs) {
+ return lhs.deadline < rhs.deadline;
+ });
+}
+
+void HangWatcher::WatchStateSnapShot::Clear() {
+ hung_watch_state_copies_.clear();
+ initialized_ = false;
+}
+
+HangWatcher::WatchStateSnapShot::WatchStateSnapShot(
+ const WatchStateSnapShot& other) = default;
+
+HangWatcher::WatchStateSnapShot::~WatchStateSnapShot() = default;
+
+std::string HangWatcher::WatchStateSnapShot::PrepareHungThreadListCrashKey()
+ const {
+ DCHECK(IsActionable());
+
+ // Build a crash key string that contains the ids of the hung threads.
+ constexpr char kSeparator{'|'};
+ std::string list_of_hung_thread_ids;
+
+ // Add as many thread ids to the crash key as possible.
+ for (const WatchStateCopy& copy : hung_watch_state_copies_) {
+ std::string fragment = base::NumberToString(copy.thread_id) + kSeparator;
+ if (list_of_hung_thread_ids.size() + fragment.size() <
+ static_cast<std::size_t>(debug::CrashKeySize::Size256)) {
+ list_of_hung_thread_ids += fragment;
+ } else {
+ // Respect the by priority ordering of thread ids in the crash key by
+ // stopping the construction as soon as one does not fit. This avoids
+ // including lesser priority ids while omitting more important ones.
+ break;
+ }
+ }
+
+ return list_of_hung_thread_ids;
+}
+
+bool HangWatcher::WatchStateSnapShot::IsActionable() const {
+ DCHECK(initialized_);
+ return !hung_watch_state_copies_.empty();
+}
+
+HangWatcher::WatchStateSnapShot HangWatcher::GrabWatchStateSnapshotForTesting()
+ const {
+ WatchStateSnapShot snapshot;
+ snapshot.Init(watch_states_, deadline_ignore_threshold_);
+ return snapshot;
+}
+
+void HangWatcher::Monitor() {
+ DCHECK_CALLED_ON_VALID_THREAD(hang_watcher_thread_checker_);
+ AutoLock auto_lock(watch_state_lock_);
+
+ // If all threads unregistered since this function was invoked there's
+ // nothing to do anymore.
+ if (watch_states_.empty())
+ return;
+
+ watch_state_snapshot_.Init(watch_states_, deadline_ignore_threshold_);
+
+ if (watch_state_snapshot_.IsActionable()) {
+ DoDumpWithoutCrashing(watch_state_snapshot_);
+ }
+
+ watch_state_snapshot_.Clear();
+}
+
+void HangWatcher::DoDumpWithoutCrashing(
+ const WatchStateSnapShot& watch_state_snapshot) {
+ TRACE_EVENT("base", "HangWatcher::DoDumpWithoutCrashing");
+
+ capture_in_progress_.store(true, std::memory_order_relaxed);
+ base::AutoLock scope_lock(capture_lock_);
+
+#if !BUILDFLAG(IS_NACL)
+ const std::string list_of_hung_thread_ids =
+ watch_state_snapshot.PrepareHungThreadListCrashKey();
+
+ static debug::CrashKeyString* crash_key = AllocateCrashKeyString(
+ "list-of-hung-threads", debug::CrashKeySize::Size256);
+
+ const debug::ScopedCrashKeyString list_of_hung_threads_crash_key_string(
+ crash_key, list_of_hung_thread_ids);
+
+ const debug::ScopedCrashKeyString
+ time_since_last_critical_memory_pressure_crash_key_string =
+ GetTimeSinceLastCriticalMemoryPressureCrashKey();
+
+ SCOPED_CRASH_KEY_STRING32("HangWatcher", "seconds-since-last-resume",
+ GetTimeSinceLastSystemPowerResumeCrashKeyValue());
+#endif
+
+ // To avoid capturing more than one hang that blames a subset of the same
+ // threads it's necessary to keep track of what is the furthest deadline
+ // that contributed to declaring a hang. Only once
+ // all threads have deadlines past this point can we be sure that a newly
+ // discovered hang is not directly related.
+ // Example:
+ // **********************************************************************
+ // Timeline A : L------1-------2----------3-------4----------N-----------
+ // Timeline B : -------2----------3-------4----------L----5------N-------
+ // Timeline C : L----------------------------5------6----7---8------9---N
+ // **********************************************************************
+ // In the example when a Monitor() happens during timeline A
+ // |deadline_ignore_threshold_| (L) is at time zero and deadlines (1-4)
+ // are before Now() (N) . A hang is captured and L is updated. During
+ // the next Monitor() (timeline B) a new deadline is over but we can't
+ // capture a hang because deadlines 2-4 are still live and already counted
+ // toward a hang. During a third monitor (timeline C) all live deadlines
+ // are now after L and a second hang can be recorded.
+ base::TimeTicks latest_expired_deadline =
+ watch_state_snapshot.GetHighestDeadline();
+
+ if (on_hang_closure_for_testing_)
+ on_hang_closure_for_testing_.Run();
+ else
+ RecordHang();
+
+ // Update after running the actual capture.
+ deadline_ignore_threshold_ = latest_expired_deadline;
+
+ capture_in_progress_.store(false, std::memory_order_relaxed);
+}
+
+void HangWatcher::SetAfterMonitorClosureForTesting(
+ base::RepeatingClosure closure) {
+ DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
+ after_monitor_closure_for_testing_ = std::move(closure);
+}
+
+void HangWatcher::SetOnHangClosureForTesting(base::RepeatingClosure closure) {
+ DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
+ on_hang_closure_for_testing_ = std::move(closure);
+}
+
+void HangWatcher::SetMonitoringPeriodForTesting(base::TimeDelta period) {
+ DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
+ monitor_period_ = period;
+}
+
+void HangWatcher::SetAfterWaitCallbackForTesting(
+ RepeatingCallback<void(TimeTicks)> callback) {
+ DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
+ after_wait_callback_ = callback;
+}
+
+void HangWatcher::SignalMonitorEventForTesting() {
+ DCHECK_CALLED_ON_VALID_THREAD(constructing_thread_checker_);
+ should_monitor_.Signal();
+}
+
+// static
+void HangWatcher::StopMonitoringForTesting() {
+ g_keep_monitoring.store(false, std::memory_order_relaxed);
+}
+
+void HangWatcher::SetTickClockForTesting(const base::TickClock* tick_clock) {
+ tick_clock_ = tick_clock;
+}
+
+void HangWatcher::BlockIfCaptureInProgress() {
+ // Makes a best-effort attempt to block execution if a hang is currently being
+ // captured. Only block on |capture_lock| if |capture_in_progress_| hints that
+ // it's already held to avoid serializing all threads on this function when no
+ // hang capture is in-progress.
+ if (capture_in_progress_.load(std::memory_order_relaxed))
+ base::AutoLock hang_lock(capture_lock_);
+}
+
+void HangWatcher::UnregisterThread() {
+ AutoLock auto_lock(watch_state_lock_);
+
+ auto it = ranges::find(
+ watch_states_,
+ internal::HangWatchState::GetHangWatchStateForCurrentThread(),
+ &std::unique_ptr<internal::HangWatchState>::get);
+
+ // Thread should be registered to get unregistered.
+ DCHECK(it != watch_states_.end());
+
+ watch_states_.erase(it);
+}
+
+namespace internal {
+namespace {
+
+constexpr uint64_t kOnlyDeadlineMask = 0x00FF'FFFF'FFFF'FFFFu;
+constexpr uint64_t kOnlyFlagsMask = ~kOnlyDeadlineMask;
+constexpr uint64_t kMaximumFlag = 0x8000'0000'0000'0000u;
+
+// Use as a mask to keep persistent flags and the deadline.
+constexpr uint64_t kPersistentFlagsAndDeadlineMask =
+ kOnlyDeadlineMask |
+ static_cast<uint64_t>(
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope);
+} // namespace
+
+// Flag binary representation assertions.
+static_assert(
+ static_cast<uint64_t>(HangWatchDeadline::Flag::kMinValue) >
+ kOnlyDeadlineMask,
+ "Invalid numerical value for flag. Would interfere with bits of data.");
+static_assert(static_cast<uint64_t>(HangWatchDeadline::Flag::kMaxValue) <=
+ kMaximumFlag,
+ "A flag can only set a single bit.");
+
+HangWatchDeadline::HangWatchDeadline() = default;
+HangWatchDeadline::~HangWatchDeadline() = default;
+
+std::pair<uint64_t, TimeTicks> HangWatchDeadline::GetFlagsAndDeadline() const {
+ uint64_t bits = bits_.load(std::memory_order_relaxed);
+ return std::make_pair(ExtractFlags(bits),
+ DeadlineFromBits(ExtractDeadline((bits))));
+}
+
+TimeTicks HangWatchDeadline::GetDeadline() const {
+ return DeadlineFromBits(
+ ExtractDeadline(bits_.load(std::memory_order_relaxed)));
+}
+
+// static
+TimeTicks HangWatchDeadline::Max() {
+ // |kOnlyDeadlineMask| has all the bits reserved for the TimeTicks value
+ // set. This means it also represents the highest representable value.
+ return DeadlineFromBits(kOnlyDeadlineMask);
+}
+
+// static
+bool HangWatchDeadline::IsFlagSet(Flag flag, uint64_t flags) {
+ return static_cast<uint64_t>(flag) & flags;
+}
+
+void HangWatchDeadline::SetDeadline(TimeTicks new_deadline) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ DCHECK(new_deadline <= Max()) << "Value too high to be represented.";
+ DCHECK(new_deadline >= TimeTicks{}) << "Value cannot be negative.";
+
+ if (switch_bits_callback_for_testing_) {
+ const uint64_t switched_in_bits = SwitchBitsForTesting();
+ // If a concurrent deadline change is tested it cannot have a deadline or
+ // persistent flag change since those always happen on the same thread.
+ DCHECK((switched_in_bits & kPersistentFlagsAndDeadlineMask) == 0u);
+ }
+
+ // Discard all non-persistent flags and apply deadline change.
+ const uint64_t old_bits = bits_.load(std::memory_order_relaxed);
+ const uint64_t new_flags =
+ ExtractFlags(old_bits & kPersistentFlagsAndDeadlineMask);
+ bits_.store(new_flags | ExtractDeadline(static_cast<uint64_t>(
+ new_deadline.ToInternalValue())),
+ std::memory_order_relaxed);
+}
+
+// TODO(crbug.com/1087026): Add flag DCHECKs here.
+bool HangWatchDeadline::SetShouldBlockOnHang(uint64_t old_flags,
+ TimeTicks old_deadline) {
+ DCHECK(old_deadline <= Max()) << "Value too high to be represented.";
+ DCHECK(old_deadline >= TimeTicks{}) << "Value cannot be negative.";
+
+ // Set the kShouldBlockOnHang flag only if |bits_| did not change since it was
+ // read. kShouldBlockOnHang is the only non-persistent flag and should never
+ // be set twice. Persistent flags and deadline changes are done from the same
+ // thread so there is no risk of losing concurrently added information.
+ uint64_t old_bits =
+ old_flags | static_cast<uint64_t>(old_deadline.ToInternalValue());
+ const uint64_t desired_bits =
+ old_bits | static_cast<uint64_t>(Flag::kShouldBlockOnHang);
+
+ // If a test needs to simulate |bits_| changing since calling this function
+ // this happens now.
+ if (switch_bits_callback_for_testing_) {
+ const uint64_t switched_in_bits = SwitchBitsForTesting();
+
+ // Injecting the flag being tested is invalid.
+ DCHECK(!IsFlagSet(Flag::kShouldBlockOnHang, switched_in_bits));
+ }
+
+ return bits_.compare_exchange_weak(old_bits, desired_bits,
+ std::memory_order_relaxed,
+ std::memory_order_relaxed);
+}
+
+void HangWatchDeadline::SetIgnoreCurrentWatchHangsInScope() {
+ SetPersistentFlag(Flag::kIgnoreCurrentWatchHangsInScope);
+}
+
+void HangWatchDeadline::UnsetIgnoreCurrentWatchHangsInScope() {
+ ClearPersistentFlag(Flag::kIgnoreCurrentWatchHangsInScope);
+}
+
+void HangWatchDeadline::SetPersistentFlag(Flag flag) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (switch_bits_callback_for_testing_)
+ SwitchBitsForTesting();
+ bits_.fetch_or(static_cast<uint64_t>(flag), std::memory_order_relaxed);
+}
+
+void HangWatchDeadline::ClearPersistentFlag(Flag flag) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ if (switch_bits_callback_for_testing_)
+ SwitchBitsForTesting();
+ bits_.fetch_and(~(static_cast<uint64_t>(flag)), std::memory_order_relaxed);
+}
+
+// static
+uint64_t HangWatchDeadline::ExtractFlags(uint64_t bits) {
+ return bits & kOnlyFlagsMask;
+}
+
+// static
+uint64_t HangWatchDeadline::ExtractDeadline(uint64_t bits) {
+ return bits & kOnlyDeadlineMask;
+}
+
+// static
+TimeTicks HangWatchDeadline::DeadlineFromBits(uint64_t bits) {
+ // |kOnlyDeadlineMask| has all the deadline bits set to 1 so is the largest
+ // representable value.
+ DCHECK(bits <= kOnlyDeadlineMask)
+ << "Flags bits are set. Remove them before returning deadline.";
+ static_assert(kOnlyDeadlineMask <= std::numeric_limits<int64_t>::max());
+ return TimeTicks::FromInternalValue(static_cast<int64_t>(bits));
+}
+
+bool HangWatchDeadline::IsFlagSet(Flag flag) const {
+ return bits_.load(std::memory_order_relaxed) & static_cast<uint64_t>(flag);
+}
+
+void HangWatchDeadline::SetSwitchBitsClosureForTesting(
+ RepeatingCallback<uint64_t(void)> closure) {
+ switch_bits_callback_for_testing_ = closure;
+}
+
+void HangWatchDeadline::ResetSwitchBitsClosureForTesting() {
+ DCHECK(switch_bits_callback_for_testing_);
+ switch_bits_callback_for_testing_.Reset();
+}
+
+uint64_t HangWatchDeadline::SwitchBitsForTesting() {
+ DCHECK(switch_bits_callback_for_testing_);
+
+ const uint64_t old_bits = bits_.load(std::memory_order_relaxed);
+ const uint64_t new_bits = switch_bits_callback_for_testing_.Run();
+ const uint64_t old_flags = ExtractFlags(old_bits);
+
+ const uint64_t switched_in_bits = old_flags | new_bits;
+ bits_.store(switched_in_bits, std::memory_order_relaxed);
+ return switched_in_bits;
+}
+
+HangWatchState::HangWatchState(HangWatcher::ThreadType thread_type)
+#if defined(STARBOARD)
+ : thread_type_(thread_type) {
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, this);
+#else
+ : resetter_(&hang_watch_state, this, nullptr), thread_type_(thread_type) {
+#endif
+// TODO(crbug.com/1223033): Remove this once macOS uses system-wide ids.
+// On macOS the thread ids used by CrashPad are not the same as the ones
+// provided by PlatformThread. Make sure to use the same for correct
+// attribution.
+#if BUILDFLAG(IS_MAC)
+ uint64_t thread_id;
+ pthread_threadid_np(pthread_self(), &thread_id);
+ thread_id_ = checked_cast<PlatformThreadId>(thread_id);
+#else
+ thread_id_ = PlatformThread::CurrentId();
+#endif
+}
+
+HangWatchState::~HangWatchState() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ DCHECK_EQ(GetHangWatchStateForCurrentThread(), this);
+
+#if DCHECK_IS_ON()
+ // Destroying the HangWatchState should not be done if there are live
+ // WatchHangsInScopes.
+ DCHECK(!current_watch_hangs_in_scope_);
+#endif
+
+#if defined(STARBOARD)
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, nullptr);
+#endif
+}
+
+// static
+std::unique_ptr<HangWatchState>
+HangWatchState::CreateHangWatchStateForCurrentThread(
+ HangWatcher::ThreadType thread_type) {
+ // Allocate a watch state object for this thread.
+ std::unique_ptr<HangWatchState> hang_state =
+ std::make_unique<HangWatchState>(thread_type);
+
+ // Setting the thread local worked.
+ DCHECK_EQ(GetHangWatchStateForCurrentThread(), hang_state.get());
+
+ // Transfer ownership to caller.
+ return hang_state;
+}
+
+TimeTicks HangWatchState::GetDeadline() const {
+ return deadline_.GetDeadline();
+}
+
+std::pair<uint64_t, TimeTicks> HangWatchState::GetFlagsAndDeadline() const {
+ return deadline_.GetFlagsAndDeadline();
+}
+
+void HangWatchState::SetDeadline(TimeTicks deadline) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ deadline_.SetDeadline(deadline);
+}
+
+bool HangWatchState::IsOverDeadline() const {
+ return TimeTicks::Now() > deadline_.GetDeadline();
+}
+
+void HangWatchState::SetIgnoreCurrentWatchHangsInScope() {
+ deadline_.SetIgnoreCurrentWatchHangsInScope();
+}
+
+void HangWatchState::UnsetIgnoreCurrentWatchHangsInScope() {
+ deadline_.UnsetIgnoreCurrentWatchHangsInScope();
+}
+
+bool HangWatchState::SetShouldBlockOnHang(uint64_t old_flags,
+ TimeTicks old_deadline) {
+ return deadline_.SetShouldBlockOnHang(old_flags, old_deadline);
+}
+
+bool HangWatchState::IsFlagSet(HangWatchDeadline::Flag flag) {
+ return deadline_.IsFlagSet(flag);
+}
+
+#if DCHECK_IS_ON()
+void HangWatchState::SetCurrentWatchHangsInScope(
+ WatchHangsInScope* current_hang_watch_scope_enable) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ current_watch_hangs_in_scope_ = current_hang_watch_scope_enable;
+}
+
+WatchHangsInScope* HangWatchState::GetCurrentWatchHangsInScope() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ return current_watch_hangs_in_scope_;
+}
+#endif
+
+HangWatchDeadline* HangWatchState::GetHangWatchDeadlineForTesting() {
+ return &deadline_;
+}
+
+void HangWatchState::IncrementNestingLevel() {
+ ++nesting_level_;
+}
+
+void HangWatchState::DecrementNestingLevel() {
+ --nesting_level_;
+}
+
+// static
+HangWatchState* HangWatchState::GetHangWatchStateForCurrentThread() {
+#if defined(STARBOARD)
+ return GetHangWatchState();
+#else
+ // Workaround false-positive MSAN use-of-uninitialized-value on
+ // thread_local storage for loaded libraries:
+ // https://github.com/google/sanitizers/issues/1265
+ MSAN_UNPOISON(&hang_watch_state, sizeof(internal::HangWatchState*));
+
+ return hang_watch_state;
+#endif
+}
+
+PlatformThreadId HangWatchState::GetThreadID() const {
+ return thread_id_;
+}
+
+} // namespace internal
+
+} // namespace base
diff --git a/base/threading/hang_watcher.h b/base/threading/hang_watcher.h
new file mode 100644
index 0000000..c74804a
--- /dev/null
+++ b/base/threading/hang_watcher.h
@@ -0,0 +1,688 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_HANG_WATCHER_H_
+#define BASE_THREADING_HANG_WATCHER_H_
+
+#include <atomic>
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+#include "base/atomicops.h"
+#include "base/auto_reset.h"
+#include "base/base_export.h"
+#include "base/bits.h"
+#include "base/compiler_specific.h"
+#include "base/dcheck_is_on.h"
+#include "base/debug/crash_logging.h"
+#include "base/functional/callback.h"
+#include "base/functional/callback_forward.h"
+#include "base/functional/callback_helpers.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/memory_pressure_listener.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ptr_exclusion.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/template_util.h"
+#include "base/thread_annotations.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+
+namespace base {
+class WatchHangsInScope;
+namespace internal {
+class HangWatchState;
+} // namespace internal
+} // namespace base
+
+namespace base {
+
+// Instantiate a WatchHangsInScope in a code scope to register to be
+// watched for hangs of more than |timeout| by the HangWatcher.
+//
+// Example usage:
+//
+// void FooBar(){
+// WatchHangsInScope scope(base::Seconds(5));
+// DoWork();
+// }
+//
+// If DoWork() takes more than 5s to run and the HangWatcher
+// inspects the thread state before Foobar returns a hang will be
+// reported.
+//
+// WatchHangsInScopes are typically meant to live on the stack. In some
+// cases it's necessary to keep a WatchHangsInScope instance as a class
+// member but special care is required when doing so as a WatchHangsInScope
+// that stays alive longer than intended will generate non-actionable hang
+// reports.
+class BASE_EXPORT [[maybe_unused, nodiscard]] WatchHangsInScope {
+ public:
+ // A good default value needs to be large enough to represent a significant
+ // hang and avoid noise while being small enough to not exclude too many
+ // hangs. The nature of the work that gets executed on the thread is also
+ // important. We can be much stricter when monitoring a UI thread compared to
+ // a ThreadPool thread for example.
+ static constexpr base::TimeDelta kDefaultHangWatchTime = base::Seconds(10);
+
+ // Constructing/destructing thread must be the same thread.
+ explicit WatchHangsInScope(TimeDelta timeout = kDefaultHangWatchTime);
+ ~WatchHangsInScope();
+
+ WatchHangsInScope(const WatchHangsInScope&) = delete;
+ WatchHangsInScope& operator=(const WatchHangsInScope&) = delete;
+
+ private:
+ // Will be true if the object actually set a deadline and false if not.
+ bool took_effect_ = true;
+
+ // This object should always be constructed and destructed on the same thread.
+ THREAD_CHECKER(thread_checker_);
+
+ // The deadline set by the previous WatchHangsInScope created on this
+ // thread. Stored so it can be restored when this WatchHangsInScope is
+ // destroyed.
+ TimeTicks previous_deadline_;
+
+ // Indicates whether the kIgnoreCurrentWatchHangsInScope flag must be set upon
+ // exiting this WatchHangsInScope if a call to InvalidateActiveExpectations()
+ // previously suspended hang watching.
+ bool set_hangs_ignored_on_exit_ = false;
+
+#if DCHECK_IS_ON()
+ // The previous WatchHangsInScope created on this thread.
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #union
+ RAW_PTR_EXCLUSION WatchHangsInScope* previous_watch_hangs_in_scope_;
+#endif
+};
+
+// Monitors registered threads for hangs by inspecting their associated
+// HangWatchStates for deadline overruns. This happens at a regular interval on
+// a separate thread. Only one instance of HangWatcher can exist at a time
+// within a single process. This instance must outlive all monitored threads.
+class BASE_EXPORT HangWatcher : public DelegateSimpleThread::Delegate {
+ public:
+ // Describes the type of a process for logging purposes.
+ enum class ProcessType {
+ kUnknownProcess = 0,
+ kBrowserProcess = 1,
+ kGPUProcess = 2,
+ kRendererProcess = 3,
+ kUtilityProcess = 4,
+ kMax = kUtilityProcess
+ };
+
+ // Describes the type of a thread for logging purposes.
+ enum class ThreadType {
+ kIOThread = 0,
+ kMainThread = 1,
+ kThreadPoolThread = 2,
+ kMax = kThreadPoolThread
+ };
+
+ // Notes on lifetime:
+ // 1) The first invocation of the constructor will set the global instance
+ // accessible through GetInstance().
+ // 2) In production HangWatcher is always purposefuly leaked.
+ // 3) If not leaked HangWatcher is always constructed and destructed from
+ // the same thread.
+ // 4) There can never be more than one instance of HangWatcher at a time.
+ // The class is not base::Singleton derived because it needs to destroyed
+ // in tests.
+ HangWatcher();
+
+ // Clears the global instance for the class.
+ ~HangWatcher() override;
+
+ HangWatcher(const HangWatcher&) = delete;
+ HangWatcher& operator=(const HangWatcher&) = delete;
+
+ static void CreateHangWatcherInstance();
+
+ // Returns a non-owning pointer to the global HangWatcher instance.
+ static HangWatcher* GetInstance();
+
+ // Initializes HangWatcher. Must be called once on the main thread during
+ // startup while single-threaded.
+ static void InitializeOnMainThread(ProcessType process_type);
+
+ // Returns the values that were set through InitializeOnMainThread() to their
+ // default value. Used for testing since in prod initialization should happen
+ // only once.
+ static void UnitializeOnMainThreadForTesting();
+
+ // Thread safe functions to verify if hang watching is activated. If called
+ // before InitializeOnMainThread returns the default value which is false.
+ static bool IsEnabled();
+ static bool IsThreadPoolHangWatchingEnabled();
+ static bool IsIOThreadHangWatchingEnabled();
+
+ // Returns true if crash dump reporting is configured for any thread type.
+ static bool IsCrashReportingEnabled();
+
+ // Use to avoid capturing hangs for operations known to take unbounded time
+ // like waiting for user input. WatchHangsInScope objects created after this
+ // call will take effect. To resume watching for hangs create a new
+ // WatchHangsInScope after the unbounded operation finishes.
+ //
+ // Example usage:
+ // {
+ // WatchHangsInScope scope_1;
+ // {
+ // WatchHangsInScope scope_2;
+ // InvalidateActiveExpectations();
+ // WaitForUserInput();
+ // }
+ //
+ // WatchHangsInScope scope_4;
+ // }
+ //
+ // WatchHangsInScope scope_5;
+ //
+ // In this example hang watching is disabled for WatchHangsInScopes 1 and 2
+ // since they were both active at the time of the invalidation.
+ // WatchHangsInScopes 4 and 5 are unaffected since they were created after the
+ // end of the WatchHangsInScope that was current at the time of invalidation.
+ //
+ static void InvalidateActiveExpectations();
+
+ // Sets up the calling thread to be monitored for threads. Returns a
+ // ScopedClosureRunner that unregisters the thread. This closure has to be
+ // called from the registered thread before it's joined. Returns a null
+ // closure in the case where there is no HangWatcher instance to register the
+ // thread with.
+ [[nodiscard]] static ScopedClosureRunner RegisterThread(
+ ThreadType thread_type);
+
+ // Choose a closure to be run at the end of each call to Monitor(). Use only
+ // for testing. Reentering the HangWatcher in the closure must be done with
+ // care. It should only be done through certain testing functions because
+ // deadlocks are possible.
+ void SetAfterMonitorClosureForTesting(base::RepeatingClosure closure);
+
+ // Choose a closure to be run instead of recording the hang. Used to test
+ // that certain conditions hold true at the time of recording. Use only
+ // for testing. Reentering the HangWatcher in the closure must be done with
+ // care. It should only be done through certain testing functions because
+ // deadlocks are possible.
+ void SetOnHangClosureForTesting(base::RepeatingClosure closure);
+
+ // Set a monitoring period other than the default. Use only for
+ // testing.
+ void SetMonitoringPeriodForTesting(base::TimeDelta period);
+
+ // Choose a callback to invoke right after waiting to monitor in Wait(). Use
+ // only for testing.
+ void SetAfterWaitCallbackForTesting(
+ RepeatingCallback<void(TimeTicks)> callback);
+
+ // Force the monitoring loop to resume and evaluate whether to continue.
+ // This can trigger a call to Monitor() or not depending on why the
+ // HangWatcher thread is sleeping. Use only for testing.
+ void SignalMonitorEventForTesting();
+
+ // Call to make sure no more monitoring takes place. The
+ // function is thread-safe and can be called at anytime but won't stop
+ // monitoring that is currently taking place. Use only for testing.
+ static void StopMonitoringForTesting();
+
+ // Replace the clock used when calculating time spent
+ // sleeping. Use only for testing.
+ void SetTickClockForTesting(const base::TickClock* tick_clock);
+
+ // Use to block until the hang is recorded. Allows the caller to halt
+ // execution so it does not overshoot the hang watch target and result in a
+ // non-actionable stack trace in the crash recorded.
+ void BlockIfCaptureInProgress();
+
+ // Begin executing the monitoring loop on the HangWatcher thread.
+ void Start();
+
+ // Returns the value of the crash key with the time since last system power
+ // resume.
+ std::string GetTimeSinceLastSystemPowerResumeCrashKeyValue() const;
+
+ private:
+ // See comment of ::RegisterThread() for details.
+ [[nodiscard]] ScopedClosureRunner RegisterThreadInternal(
+ ThreadType thread_type) LOCKS_EXCLUDED(watch_state_lock_);
+
+ // Use to assert that functions are called on the monitoring thread.
+ THREAD_CHECKER(hang_watcher_thread_checker_);
+
+ // Use to assert that functions are called on the constructing thread.
+ THREAD_CHECKER(constructing_thread_checker_);
+
+ // Invoked on memory pressure signal.
+ void OnMemoryPressure(
+ base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
+
+#if !BUILDFLAG(IS_NACL)
+ // Returns a ScopedCrashKeyString that sets the crash key with the time since
+ // last critical memory pressure signal.
+ [[nodiscard]] debug::ScopedCrashKeyString
+ GetTimeSinceLastCriticalMemoryPressureCrashKey();
+#endif
+
+ // Invoke base::debug::DumpWithoutCrashing() insuring that the stack frame
+ // right under it in the trace belongs to HangWatcher for easier attribution.
+ NOINLINE static void RecordHang();
+
+ using HangWatchStates =
+ std::vector<std::unique_ptr<internal::HangWatchState>>;
+
+ // Used to save a snapshots of the state of hang watching during capture.
+ // Only the state of hung threads is retained.
+ class BASE_EXPORT WatchStateSnapShot {
+ public:
+ struct WatchStateCopy {
+ base::TimeTicks deadline;
+ base::PlatformThreadId thread_id;
+ };
+
+ WatchStateSnapShot();
+ WatchStateSnapShot(const WatchStateSnapShot& other);
+ ~WatchStateSnapShot();
+
+ // Initialize the snapshot from provided data. |snapshot_time| can be
+ // different than now() to be coherent with other operations recently done
+ // on |watch_states|. |hung_watch_state_copies_| can be empty after
+ // initialization for a number of reasons:
+ // 1. If any deadline in |watch_states| is before
+ // |deadline_ignore_threshold|.
+ // 2. If some of the hung threads could not be marked as blocking on
+ // capture.
+ // 3. If none of the hung threads are of a type configured to trigger a
+ // crash dump.
+ //
+ // This function cannot be called more than once without an associated call
+ // to Clear().
+ void Init(const HangWatchStates& watch_states,
+ base::TimeTicks deadline_ignore_threshold);
+
+ // Reset the snapshot object to be reused. Can only be called after Init().
+ void Clear();
+
+ // Returns a string that contains the ids of the hung threads separated by a
+ // '|'. The size of the string is capped at debug::CrashKeySize::Size256. If
+ // no threads are hung returns an empty string. Can only be invoked if
+ // IsActionable(). Can only be called after Init().
+ std::string PrepareHungThreadListCrashKey() const;
+
+ // Return the highest deadline included in this snapshot. Can only be called
+ // if IsActionable(). Can only be called after Init().
+ base::TimeTicks GetHighestDeadline() const;
+
+ // Returns true if the snapshot can be used to record an actionable hang
+ // report and false if not. Can only be called after Init().
+ bool IsActionable() const;
+
+ private:
+ bool initialized_ = false;
+ std::vector<WatchStateCopy> hung_watch_state_copies_;
+ };
+
+ // Return a watch state snapshot taken Now() to be inspected in tests.
+ // NO_THREAD_SAFETY_ANALYSIS is needed because the analyzer can't figure out
+ // that calls to this function done from |on_hang_closure_| are properly
+ // locked.
+ WatchStateSnapShot GrabWatchStateSnapshotForTesting() const
+ NO_THREAD_SAFETY_ANALYSIS;
+
+ // Inspects the state of all registered threads to check if they are hung and
+ // invokes the appropriate closure if so.
+ void Monitor() LOCKS_EXCLUDED(watch_state_lock_);
+
+ // Record the hang crash dump and perform the necessary housekeeping before
+ // and after.
+ void DoDumpWithoutCrashing(const WatchStateSnapShot& watch_state_snapshot)
+ EXCLUSIVE_LOCKS_REQUIRED(watch_state_lock_) LOCKS_EXCLUDED(capture_lock_);
+
+ // Stop all monitoring and join the HangWatcher thread.
+ void Stop();
+
+ // Wait until it's time to monitor.
+ void Wait();
+
+ // Run the loop that periodically monitors the registered thread at a
+ // set time interval.
+ void Run() override;
+
+ base::TimeDelta monitor_period_;
+
+ // Use to make the HangWatcher thread wake or sleep to schedule the
+ // appropriate monitoring frequency.
+ WaitableEvent should_monitor_;
+
+ bool IsWatchListEmpty() LOCKS_EXCLUDED(watch_state_lock_);
+
+ // Stops hang watching on the calling thread by removing the entry from the
+ // watch list.
+ void UnregisterThread() LOCKS_EXCLUDED(watch_state_lock_);
+
+ Lock watch_state_lock_;
+
+ std::vector<std::unique_ptr<internal::HangWatchState>> watch_states_
+ GUARDED_BY(watch_state_lock_);
+
+ // Snapshot to be reused across hang captures. The point of keeping it
+ // around is reducing allocations during capture.
+ WatchStateSnapShot watch_state_snapshot_
+ GUARDED_BY_CONTEXT(hang_watcher_thread_checker_);
+
+ base::DelegateSimpleThread thread_;
+
+ RepeatingClosure after_monitor_closure_for_testing_;
+ RepeatingClosure on_hang_closure_for_testing_;
+ RepeatingCallback<void(TimeTicks)> after_wait_callback_;
+
+ base::Lock capture_lock_ ACQUIRED_AFTER(watch_state_lock_);
+ std::atomic<bool> capture_in_progress_{false};
+
+ raw_ptr<const base::TickClock> tick_clock_;
+
+ // Registration to receive memory pressure signals.
+ base::MemoryPressureListener memory_pressure_listener_;
+
+ // The last time at which a critical memory pressure signal was received, or
+ // null if no signal was ever received. Atomic because it's set and read from
+ // different threads.
+ std::atomic<base::TimeTicks> last_critical_memory_pressure_{
+ base::TimeTicks()};
+
+ // The time after which all deadlines in |watch_states_| need to be for a hang
+ // to be reported.
+ base::TimeTicks deadline_ignore_threshold_;
+
+ FRIEND_TEST_ALL_PREFIXES(HangWatcherTest, NestedScopes);
+ FRIEND_TEST_ALL_PREFIXES(HangWatcherSnapshotTest, HungThreadIDs);
+ FRIEND_TEST_ALL_PREFIXES(HangWatcherSnapshotTest, NonActionableReport);
+};
+
+// Classes here are exposed in the header only for testing. They are not
+// intended to be used outside of base.
+namespace internal {
+
+// Threadsafe class that manages a deadline of type TimeTicks alongside hang
+// watching specific flags. The flags are stored in the higher bits of the
+// underlying TimeTicks deadline. This enables setting the flags on thread T1 in
+// a way that's resilient to concurrent deadline or flag changes from thread T2.
+// Flags can be queried separately from the deadline and users of this class
+// should not have to care about them when doing so.
+class BASE_EXPORT HangWatchDeadline {
+ public:
+ // Masks to set flags by flipping a single bit in the TimeTicks value. There
+ // are two types of flags. Persistent flags remain set through a deadline
+ // change and non-persistent flags are cleared when the deadline changes.
+ enum class Flag : uint64_t {
+ // Minimum value for validation purposes. Not currently used.
+ kMinValue = bits::LeftmostBit<uint64_t>() >> 7,
+ // Persistent because if hang detection is disabled on a thread it should
+ // be re-enabled manually.
+ kIgnoreCurrentWatchHangsInScope = bits::LeftmostBit<uint64_t>() >> 1,
+ // Non-persistent because a new value means a new WatchHangsInScope started
+ // after the beginning of capture. It can't be implicated in the hang so we
+ // don't want it to block.
+ kShouldBlockOnHang = bits::LeftmostBit<uint64_t>() >> 0,
+ kMaxValue = kShouldBlockOnHang
+ };
+
+ HangWatchDeadline();
+ ~HangWatchDeadline();
+
+ // HangWatchDeadline should never be copied. To keep a copy of the deadline or
+ // flags use the appropriate accessors.
+ HangWatchDeadline(const HangWatchDeadline&) = delete;
+ HangWatchDeadline& operator=(const HangWatchDeadline&) = delete;
+
+ // Returns the underlying TimeTicks deadline. WARNING: The deadline and flags
+ // can change concurrently. To inspect both, use GetFlagsAndDeadline() to get
+ // a coherent race-free view of the state.
+ TimeTicks GetDeadline() const;
+
+ // Returns a mask containing the flags and the deadline as a pair. Use to
+ // inspect the flags and deadline and then optionally call
+ // SetShouldBlockOnHang() .
+ std::pair<uint64_t, TimeTicks> GetFlagsAndDeadline() const;
+
+ // Returns true if the flag is set and false if not. WARNING: The deadline and
+ // flags can change concurrently. To inspect both, use GetFlagsAndDeadline()
+ // to get a coherent race-free view of the state.
+ bool IsFlagSet(Flag flag) const;
+
+ // Returns true if a flag is set in |flags| and false if not. Use to inspect
+ // the flags mask returned by GetFlagsAndDeadline(). WARNING: The deadline and
+ // flags can change concurrently. If you need to inspect both you need to use
+ // GetFlagsAndDeadline() to get a coherent race-free view of the state.
+ static bool IsFlagSet(Flag flag, uint64_t flags);
+
+ // Replace the deadline value. |new_value| needs to be within [0,
+ // Max()]. This function can never fail.
+ void SetDeadline(TimeTicks new_value);
+
+ // Sets the kShouldBlockOnHang flag and returns true if current flags and
+ // deadline are still equal to |old_flags| and |old_deadline|. Otherwise does
+ // not set the flag and returns false.
+ bool SetShouldBlockOnHang(uint64_t old_flags, TimeTicks old_deadline);
+
+ // Sets the kIgnoreCurrentWatchHangsInScope flag.
+ void SetIgnoreCurrentWatchHangsInScope();
+
+ // Clears the kIgnoreCurrentWatchHangsInScope flag.
+ void UnsetIgnoreCurrentWatchHangsInScope();
+
+ // Use to simulate the value of |bits_| changing between the calling a
+ // Set* function and the moment of atomically switching the values. The
+ // callback should return a value containing the desired flags and deadline
+ // bits. The flags that are already set will be preserved upon applying. Use
+ // only for testing.
+ void SetSwitchBitsClosureForTesting(
+ RepeatingCallback<uint64_t(void)> closure);
+
+ // Remove the deadline modification callback for when testing is done. Use
+ // only for testing.
+ void ResetSwitchBitsClosureForTesting();
+
+ private:
+ using TimeTicksInternalRepresentation =
+ std::invoke_result<decltype(&TimeTicks::ToInternalValue),
+ TimeTicks>::type;
+ static_assert(std::is_same<TimeTicksInternalRepresentation, int64_t>::value,
+ "Bit manipulations made by HangWatchDeadline need to be"
+ "adapted if internal representation of TimeTicks changes.");
+
+ // Replace the bits with the ones provided through the callback. Preserves the
+ // flags that were already set. Returns the switched in bits. Only call if
+ // |switch_bits_callback_for_testing_| is installed.
+ uint64_t SwitchBitsForTesting();
+
+ // Atomically sets persitent flag |flag|. Cannot fail.
+ void SetPersistentFlag(Flag flag);
+
+ // Atomically clears persitent flag |flag|. Cannot fail.
+ void ClearPersistentFlag(Flag flag);
+
+ // Converts bits to TimeTicks with some sanity checks. Use to return the
+ // deadline outside of this class.
+ static TimeTicks DeadlineFromBits(uint64_t bits);
+
+ // Returns the largest representable deadline.
+ static TimeTicks Max();
+
+ // Extract the flag bits from |bits|.
+ static uint64_t ExtractFlags(uint64_t bits);
+
+ // Extract the deadline bits from |bits|.
+ static uint64_t ExtractDeadline(uint64_t bits);
+
+ // BitsType is uint64_t. This type is chosen for having
+ // std::atomic<BitsType>{}.is_lock_free() true on many platforms and having no
+ // undefined behaviors with regards to bit shift operations. Throughout this
+ // class this is the only type that is used to store, retrieve and manipulate
+ // the bits. When returning a TimeTicks value outside this class it's
+ // necessary to run the proper checks to insure correctness of the conversion
+ // that has to go through int_64t. (See DeadlineFromBits()).
+ using BitsType = uint64_t;
+ static_assert(std::is_same<std::underlying_type<Flag>::type, BitsType>::value,
+ "Flag should have the same underlying type as bits_ to "
+ "simplify thinking about bit operations");
+
+ // Holds the bits of both the flags and the TimeTicks deadline.
+ // TimeTicks values represent a count of microseconds since boot which may or
+ // may not include suspend time depending on the platform. Using the seven
+ // highest order bits and the sign bit to store flags still enables the
+ // storing of TimeTicks values that can represent up to ~1142 years of uptime
+ // in the remaining bits. Should never be directly accessed from outside the
+ // class. Starts out at Max() to provide a base-line deadline that will not be
+ // reached during normal execution.
+ //
+ // Binary format: 0xFFDDDDDDDDDDDDDDDD
+ // F = Flags
+ // D = Deadline
+ std::atomic<BitsType> bits_{static_cast<uint64_t>(Max().ToInternalValue())};
+
+ RepeatingCallback<uint64_t(void)> switch_bits_callback_for_testing_;
+
+ THREAD_CHECKER(thread_checker_);
+
+ FRIEND_TEST_ALL_PREFIXES(HangWatchDeadlineTest, BitsPreservedThroughExtract);
+};
+
+// Contains the information necessary for hang watching a specific
+// thread. Instances of this class are accessed concurrently by the associated
+// thread and the HangWatcher. The HangWatcher owns instances of this
+// class and outside of it they are accessed through
+// GetHangWatchStateForCurrentThread().
+class BASE_EXPORT HangWatchState {
+ public:
+ // |thread_type| is the type of thread the watch state will
+ // be associated with. It's the responsibility of the creating
+ // code to choose the correct type.
+ explicit HangWatchState(HangWatcher::ThreadType thread_type);
+ ~HangWatchState();
+
+ HangWatchState(const HangWatchState&) = delete;
+ HangWatchState& operator=(const HangWatchState&) = delete;
+
+ // Allocates a new state object bound to the calling thread and returns an
+ // owning pointer to it.
+ static std::unique_ptr<HangWatchState> CreateHangWatchStateForCurrentThread(
+ HangWatcher::ThreadType thread_type);
+
+ // Retrieves the hang watch state associated with the calling thread.
+ // Returns nullptr if no HangWatchState exists for the current thread (see
+ // CreateHangWatchStateForCurrentThread()).
+ static HangWatchState* GetHangWatchStateForCurrentThread();
+
+ // Returns the current deadline. Use this function if you need to
+ // store the value. To test if the deadline has expired use IsOverDeadline().
+ // WARNING: The deadline and flags can change concurrently. If you need to
+ // inspect both you need to use GetFlagsAndDeadline() to get a coherent
+ // race-free view of the state.
+ TimeTicks GetDeadline() const;
+
+ // Returns a mask containing the hang watching flags and the value as a pair.
+ // Use to inspect the flags and deadline and optionally call
+ // SetShouldBlockOnHang(flags, deadline).
+ std::pair<uint64_t, TimeTicks> GetFlagsAndDeadline() const;
+
+ // Sets the deadline to a new value.
+ void SetDeadline(TimeTicks deadline);
+
+ // Mark this thread as ignored for hang watching. This means existing
+ // WatchHangsInScope will not trigger hangs.
+ void SetIgnoreCurrentWatchHangsInScope();
+
+ // Reactivate hang watching on this thread. Should be called when all
+ // WatchHangsInScope instances that were ignored have completed.
+ void UnsetIgnoreCurrentWatchHangsInScope();
+
+ // Mark the current state as having to block in its destruction until hang
+ // capture completes.
+ bool SetShouldBlockOnHang(uint64_t old_flags, TimeTicks old_deadline);
+
+ // Returns true if |flag| is set and false if not. WARNING: The deadline and
+ // flags can change concurrently. If you need to inspect both you need to use
+ // GetFlagsAndDeadline() to get a coherent race-free view of the state.
+ bool IsFlagSet(HangWatchDeadline::Flag flag);
+
+ // Tests whether the associated thread's execution has gone over the deadline.
+ bool IsOverDeadline() const;
+
+#if DCHECK_IS_ON()
+ // Saves the supplied WatchHangsInScope as the currently active
+ // WatchHangsInScope.
+ void SetCurrentWatchHangsInScope(WatchHangsInScope* scope);
+
+ // Retrieve the currently active scope.
+ WatchHangsInScope* GetCurrentWatchHangsInScope();
+#endif
+
+ PlatformThreadId GetThreadID() const;
+
+ // Retrieve the current hang watch deadline directly. For testing only.
+ HangWatchDeadline* GetHangWatchDeadlineForTesting();
+
+ // Returns the current nesting level.
+ int nesting_level() { return nesting_level_; }
+
+ // Increase the nesting level by 1;
+ void IncrementNestingLevel();
+
+ // Reduce the nesting level by 1;
+ void DecrementNestingLevel();
+
+ // Returns the type of the thread under watch.
+ HangWatcher::ThreadType thread_type() const { return thread_type_; }
+
+ private:
+ // The thread that creates the instance should be the class that updates
+ // the deadline.
+ THREAD_CHECKER(thread_checker_);
+
+#if !defined(STARBOARD)
+ const AutoReset<HangWatchState*> resetter_;
+#endif
+
+ // If the deadline fails to be updated before TimeTicks::Now() ever
+ // reaches the value contained in it this constistutes a hang.
+ HangWatchDeadline deadline_;
+
+ // A unique ID of the thread under watch. Used for logging in crash reports
+ // only.
+ PlatformThreadId thread_id_;
+
+ // Number of active HangWatchScopeEnables on this thread.
+ int nesting_level_ = 0;
+
+ // The type of the thread under watch.
+ const HangWatcher::ThreadType thread_type_;
+
+#if DCHECK_IS_ON()
+ // Used to keep track of the current WatchHangsInScope and detect improper
+ // usage. Scopes should always be destructed in reverse order from the one
+ // they were constructed in. Example of improper use:
+ //
+ // {
+ // std::unique_ptr<Scope> scope = std::make_unique<Scope>(...);
+ // Scope other_scope;
+ // |scope| gets deallocated first, violating reverse destruction order.
+ // scope.reset();
+ // }
+ raw_ptr<WatchHangsInScope> current_watch_hangs_in_scope_{nullptr};
+#endif
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_THREADING_HANG_WATCHER_H_
diff --git a/base/threading/hang_watcher_unittest.cc b/base/threading/hang_watcher_unittest.cc
new file mode 100644
index 0000000..badffab
--- /dev/null
+++ b/base/threading/hang_watcher_unittest.cc
@@ -0,0 +1,1293 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/hang_watcher.h"
+#include <atomic>
+#include <memory>
+
+#include "base/barrier_closure.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/power_monitor_test.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/threading_features.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+using testing::ElementsAre;
+using testing::IsEmpty;
+
+namespace base {
+namespace {
+
+// Use with a FeatureList to activate crash dumping for threads marked as
+// threadpool threads.
+const std::vector<base::test::FeatureRefAndParams> kFeatureAndParams{
+ {base::kEnableHangWatcher, {{"ui_thread_log_level", "2"}}}};
+
+// Use this value to mark things very far off in the future. Adding this
+// to TimeTicks::Now() gives a point that will never be reached during the
+// normal execution of a test.
+constexpr TimeDelta kVeryLongDelta{base::Days(365)};
+
+// A relatively small time delta to ensure ordering of hung threads list.
+constexpr TimeDelta kSmallCPUQuantum{base::Milliseconds(1)};
+
+constexpr uint64_t kArbitraryDeadline = 0x0000C0FFEEC0FFEEu;
+constexpr uint64_t kAllOnes = 0xFFFFFFFFFFFFFFFFu;
+constexpr uint64_t kAllZeros = 0x0000000000000000u;
+constexpr uint64_t kOnesThenZeroes = 0xAAAAAAAAAAAAAAAAu;
+constexpr uint64_t kZeroesThenOnes = 0x5555555555555555u;
+
+// Waits on provided WaitableEvent before executing and signals when done.
+class BlockingThread : public DelegateSimpleThread::Delegate {
+ public:
+ explicit BlockingThread(base::WaitableEvent* unblock_thread,
+ base::TimeDelta timeout)
+ : thread_(this, "BlockingThread"),
+ unblock_thread_(unblock_thread),
+ timeout_(timeout) {}
+
+ ~BlockingThread() override = default;
+
+ void Run() override {
+ // (Un)Register the thread here instead of in ctor/dtor so that the action
+ // happens on the right thread.
+ base::ScopedClosureRunner unregister_closure =
+ base::HangWatcher::RegisterThread(
+ base::HangWatcher::ThreadType::kMainThread);
+
+ WatchHangsInScope scope(timeout_);
+ wait_until_entered_scope_.Signal();
+
+ unblock_thread_->Wait();
+ run_event_.Signal();
+ }
+
+ bool IsDone() { return run_event_.IsSignaled(); }
+
+ void StartAndWaitForScopeEntered() {
+ thread_.Start();
+ // Block until this thread registered itself for hang watching and has
+ // entered a WatchHangsInScope.
+ wait_until_entered_scope_.Wait();
+ }
+
+ void Join() { thread_.Join(); }
+
+ PlatformThreadId GetId() { return thread_.tid(); }
+
+ private:
+ base::DelegateSimpleThread thread_;
+
+ // Will be signaled once the thread is properly registered for watching and
+ // the WatchHangsInScope has been entered.
+ WaitableEvent wait_until_entered_scope_;
+
+ // Will be signaled once ThreadMain has run.
+ WaitableEvent run_event_;
+
+ const raw_ptr<base::WaitableEvent> unblock_thread_;
+
+ base::TimeDelta timeout_;
+};
+
+class HangWatcherTest : public testing::Test {
+ public:
+ const base::TimeDelta kTimeout = base::Seconds(10);
+ const base::TimeDelta kHangTime = kTimeout + base::Seconds(1);
+
+ HangWatcherTest() {
+ feature_list_.InitWithFeaturesAndParameters(kFeatureAndParams, {});
+ hang_watcher_.InitializeOnMainThread(
+ HangWatcher::ProcessType::kBrowserProcess);
+
+ hang_watcher_.SetAfterMonitorClosureForTesting(base::BindRepeating(
+ &WaitableEvent::Signal, base::Unretained(&monitor_event_)));
+
+ hang_watcher_.SetOnHangClosureForTesting(base::BindRepeating(
+ &WaitableEvent::Signal, base::Unretained(&hang_event_)));
+
+ // We're not testing the monitoring loop behavior in this test so we want to
+ // trigger monitoring manually.
+ hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta);
+
+ // Start the monitoring loop.
+ hang_watcher_.Start();
+ }
+
+ void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); }
+
+ HangWatcherTest(const HangWatcherTest& other) = delete;
+ HangWatcherTest& operator=(const HangWatcherTest& other) = delete;
+
+ protected:
+ // Used to wait for monitoring. Will be signaled by the HangWatcher thread and
+ // so needs to outlive it.
+ WaitableEvent monitor_event_;
+
+ // Signaled from the HangWatcher thread when a hang is detected. Needs to
+ // outlive the HangWatcher thread.
+ WaitableEvent hang_event_;
+
+ base::test::ScopedFeatureList feature_list_;
+
+ // Used exclusively for MOCK_TIME. No tasks will be run on the environment.
+ // Single threaded to avoid ThreadPool WorkerThreads registering.
+ test::SingleThreadTaskEnvironment task_environment_{
+ test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+ // This must be declared last (after task_environment_, for example) so that
+ // the watcher thread is joined before objects like the mock timer are
+ // destroyed, causing racy crashes.
+ HangWatcher hang_watcher_;
+};
+
+class HangWatcherBlockingThreadTest : public HangWatcherTest {
+ public:
+ HangWatcherBlockingThreadTest() : thread_(&unblock_thread_, kTimeout) {}
+
+ HangWatcherBlockingThreadTest(const HangWatcherBlockingThreadTest& other) =
+ delete;
+ HangWatcherBlockingThreadTest& operator=(
+ const HangWatcherBlockingThreadTest& other) = delete;
+
+ protected:
+ void JoinThread() {
+ unblock_thread_.Signal();
+
+ // Thread is joinable since we signaled |unblock_thread_|.
+ thread_.Join();
+
+ // If thread is done then it signaled.
+ ASSERT_TRUE(thread_.IsDone());
+ }
+
+ void StartBlockedThread() {
+ // Thread has not run yet.
+ ASSERT_FALSE(thread_.IsDone());
+
+ // Start the thread. It will block since |unblock_thread_| was not
+ // signaled yet.
+ thread_.StartAndWaitForScopeEntered();
+
+ // Thread registration triggered a call to HangWatcher::Monitor() which
+ // signaled |monitor_event_|. Reset it so it's ready for waiting later on.
+ monitor_event_.Reset();
+ }
+
+ void MonitorHangs() {
+ // HangWatcher::Monitor() should not be set which would mean a call to
+ // HangWatcher::Monitor() happened and was unacounted for.
+ // ASSERT_FALSE(monitor_event_.IsSignaled());
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+ }
+
+ // Used to unblock the monitored thread. Signaled from the test main thread.
+ WaitableEvent unblock_thread_;
+
+ BlockingThread thread_;
+};
+} // namespace
+
+TEST_F(HangWatcherTest, InvalidatingExpectationsPreventsCapture) {
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ // Create a hang.
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+
+ // de-activate hang watching,
+ base::HangWatcher::InvalidateActiveExpectations();
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ // Hang is not detected.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+ ASSERT_FALSE(hang_event_.IsSignaled());
+}
+
+TEST_F(HangWatcherTest, MultipleInvalidateExpectationsDoNotCancelOut) {
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ // Create a hang.
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+
+ // de-activate hang watching,
+ base::HangWatcher::InvalidateActiveExpectations();
+
+ // Redundently de-activate hang watching.
+ base::HangWatcher::InvalidateActiveExpectations();
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ // Hang is not detected.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+ ASSERT_FALSE(hang_event_.IsSignaled());
+}
+
+TEST_F(HangWatcherTest, NewInnerWatchHangsInScopeAfterInvalidationDetectsHang) {
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+
+ // De-activate hang watching.
+ base::HangWatcher::InvalidateActiveExpectations();
+
+ {
+ WatchHangsInScope also_expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+
+ // Hang is detected since the new WatchHangsInScope temporarily
+ // re-activated hang_watching.
+ monitor_event_.Wait();
+ ASSERT_TRUE(hang_event_.IsSignaled());
+ }
+
+ // Reset to attempt capture again.
+ monitor_event_.Reset();
+ hang_event_.Reset();
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+
+ // Hang is not detected since execution is back to being covered by
+ // |expires_instantly| for which expectations were invalidated.
+ monitor_event_.Wait();
+ ASSERT_FALSE(hang_event_.IsSignaled());
+}
+
+TEST_F(HangWatcherTest,
+ NewSeparateWatchHangsInScopeAfterInvalidationDetectsHang) {
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ {
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+
+ // De-activate hang watching.
+ base::HangWatcher::InvalidateActiveExpectations();
+ }
+
+ WatchHangsInScope also_expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+
+ // Hang is detected since the new WatchHangsInScope did not have its
+ // expectations invalidated.
+ monitor_event_.Wait();
+ ASSERT_TRUE(hang_event_.IsSignaled());
+}
+
+// Test that invalidating expectations from inner WatchHangsInScope will also
+// prevent hang detection in outer scopes.
+TEST_F(HangWatcherTest, ScopeDisabledObjectInnerScope) {
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ // Start a WatchHangsInScope that expires right away. Then advance
+ // time to make sure no hang is detected.
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+ {
+ WatchHangsInScope also_expires_instantly(base::TimeDelta{});
+
+ // De-activate hang watching.
+ base::HangWatcher::InvalidateActiveExpectations();
+ task_environment_.FastForwardBy(kHangTime);
+ }
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+
+ // Hang is ignored since it concerns a scope for which one of the inner scope
+ // was ignored.
+ ASSERT_FALSE(hang_event_.IsSignaled());
+}
+
+TEST_F(HangWatcherTest, NewScopeAfterDisabling) {
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ // Start a WatchHangsInScope that expires right away. Then advance
+ // time to make sure no hang is detected.
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+ {
+ WatchHangsInScope also_expires_instantly(base::TimeDelta{});
+
+ // De-activate hang watching.
+ base::HangWatcher::InvalidateActiveExpectations();
+ task_environment_.FastForwardBy(kHangTime);
+ }
+
+ // New scope for which expecations are never invalidated.
+ WatchHangsInScope also_expires_instantly(base::TimeDelta{});
+ task_environment_.FastForwardBy(kHangTime);
+
+ // Trigger a monitoring on HangWatcher thread and verify results.
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+
+ // Hang is detected because it's unrelated to the hangs that were disabled.
+ ASSERT_TRUE(hang_event_.IsSignaled());
+}
+
+TEST_F(HangWatcherTest, NestedScopes) {
+ // Create a state object for the test thread since this test is single
+ // threaded.
+ auto current_hang_watch_state =
+ base::internal::HangWatchState::CreateHangWatchStateForCurrentThread(
+ HangWatcher::ThreadType::kMainThread);
+
+ ASSERT_FALSE(current_hang_watch_state->IsOverDeadline());
+ base::TimeTicks original_deadline = current_hang_watch_state->GetDeadline();
+
+ constexpr base::TimeDelta kFirstTimeout(base::Milliseconds(500));
+ base::TimeTicks first_deadline = base::TimeTicks::Now() + kFirstTimeout;
+
+ constexpr base::TimeDelta kSecondTimeout(base::Milliseconds(250));
+ base::TimeTicks second_deadline = base::TimeTicks::Now() + kSecondTimeout;
+
+ // At this point we have not set any timeouts.
+ {
+ // Create a first timeout which is more restrictive than the default.
+ WatchHangsInScope first_scope(kFirstTimeout);
+
+ // We are on mock time. There is no time advancement and as such no hangs.
+ ASSERT_FALSE(current_hang_watch_state->IsOverDeadline());
+ ASSERT_EQ(current_hang_watch_state->GetDeadline(), first_deadline);
+ {
+ // Set a yet more restrictive deadline. Still no hang.
+ WatchHangsInScope second_scope(kSecondTimeout);
+ ASSERT_FALSE(current_hang_watch_state->IsOverDeadline());
+ ASSERT_EQ(current_hang_watch_state->GetDeadline(), second_deadline);
+ }
+ // First deadline we set should be restored.
+ ASSERT_FALSE(current_hang_watch_state->IsOverDeadline());
+ ASSERT_EQ(current_hang_watch_state->GetDeadline(), first_deadline);
+ }
+
+ // Original deadline should now be restored.
+ ASSERT_FALSE(current_hang_watch_state->IsOverDeadline());
+ ASSERT_EQ(current_hang_watch_state->GetDeadline(), original_deadline);
+}
+
+TEST_F(HangWatcherBlockingThreadTest, HistogramsLoggedOnHang) {
+ base::HistogramTester histogram_tester;
+ StartBlockedThread();
+
+ // Simulate hang.
+ task_environment_.FastForwardBy(kHangTime);
+
+ // First monitoring catches the hang and emits the histogram.
+ MonitorHangs();
+ EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung."
+ "BrowserProcess.UIThread"),
+ ElementsAre(base::Bucket(true, /*count=*/1)));
+
+ // Reset to attempt capture again.
+ hang_event_.Reset();
+ monitor_event_.Reset();
+
+ // Hang is logged again even if it would not trigger a crash dump.
+ MonitorHangs();
+ EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung."
+ "BrowserProcess.UIThread"),
+ ElementsAre(base::Bucket(true, /*count=*/2)));
+
+ // Thread types that are not monitored should not get any samples.
+ EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung."
+ "BrowserProcess.IOThread"),
+ IsEmpty());
+ JoinThread();
+}
+
+TEST_F(HangWatcherBlockingThreadTest, HistogramsLoggedWithoutHangs) {
+ base::HistogramTester histogram_tester;
+ StartBlockedThread();
+
+ // No hang to catch so nothing is recorded.
+ MonitorHangs();
+ ASSERT_FALSE(hang_event_.IsSignaled());
+
+ // A thread of type ThreadForTesting was monitored but didn't hang. This is
+ // logged.
+ EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung."
+ "BrowserProcess.UIThread"),
+ ElementsAre(base::Bucket(false, /*count=*/1)));
+
+ // Thread types that are not monitored should not get any samples.
+ EXPECT_THAT(histogram_tester.GetAllSamples("HangWatcher.IsThreadHung."
+ "BrowserProcess.IOThread"),
+ IsEmpty());
+ JoinThread();
+}
+
+TEST_F(HangWatcherBlockingThreadTest, Hang) {
+ StartBlockedThread();
+
+ // Simulate hang.
+ task_environment_.FastForwardBy(kHangTime);
+
+ // First monitoring catches and records the hang.
+ MonitorHangs();
+ ASSERT_TRUE(hang_event_.IsSignaled());
+
+ JoinThread();
+}
+
+TEST_F(HangWatcherBlockingThreadTest, HangAlreadyRecorded) {
+ StartBlockedThread();
+
+ // Simulate hang.
+ task_environment_.FastForwardBy(kHangTime);
+
+ // First monitoring catches and records the hang.
+ MonitorHangs();
+ ASSERT_TRUE(hang_event_.IsSignaled());
+
+ // Reset to attempt capture again.
+ hang_event_.Reset();
+ monitor_event_.Reset();
+
+ // Second monitoring does not record because a hang that was already recorded
+ // is still live.
+ MonitorHangs();
+ ASSERT_FALSE(hang_event_.IsSignaled());
+
+ JoinThread();
+}
+
+TEST_F(HangWatcherBlockingThreadTest, NoHang) {
+ StartBlockedThread();
+
+ // No hang to catch so nothing is recorded.
+ MonitorHangs();
+ ASSERT_FALSE(hang_event_.IsSignaled());
+
+ JoinThread();
+}
+
+namespace {
+class HangWatcherSnapshotTest : public testing::Test {
+ public:
+ void SetUp() override {
+ feature_list_.InitWithFeaturesAndParameters(kFeatureAndParams, {});
+ hang_watcher_.InitializeOnMainThread(
+ HangWatcher::ProcessType::kBrowserProcess);
+
+ // The monitoring loop behavior is not verified in this test so we want to
+ // trigger monitoring manually.
+ hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta);
+ }
+
+ void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); }
+
+ HangWatcherSnapshotTest() = default;
+ HangWatcherSnapshotTest(const HangWatcherSnapshotTest& other) = delete;
+ HangWatcherSnapshotTest& operator=(const HangWatcherSnapshotTest& other) =
+ delete;
+
+ protected:
+ void TriggerMonitorAndWaitForCompletion() {
+ monitor_event_.Reset();
+ hang_watcher_.SignalMonitorEventForTesting();
+ monitor_event_.Wait();
+ }
+
+ // Verify that a capture takes place and that at the time of the capture the
+ // list of hung thread ids is correct.
+ void TestIDList(const std::string& id_list) {
+ list_of_hung_thread_ids_during_capture_ = id_list;
+ task_environment_.AdvanceClock(kSmallCPUQuantum);
+ TriggerMonitorAndWaitForCompletion();
+ ASSERT_EQ(++reference_capture_count_, hang_capture_count_);
+ }
+
+ // Verify that even if hang monitoring takes place no hangs are detected.
+ void ExpectNoCapture() {
+ int old_capture_count = hang_capture_count_;
+ task_environment_.AdvanceClock(kSmallCPUQuantum);
+ TriggerMonitorAndWaitForCompletion();
+ ASSERT_EQ(old_capture_count, hang_capture_count_);
+ }
+
+ std::string ConcatenateThreadIds(
+ const std::vector<base::PlatformThreadId>& ids) const {
+ std::string result;
+ constexpr char kSeparator{'|'};
+
+ for (PlatformThreadId id : ids) {
+ result += base::NumberToString(id) + kSeparator;
+ }
+
+ return result;
+ }
+
+ // Will be signaled once monitoring took place. Marks the end of the test.
+ WaitableEvent monitor_event_;
+
+ const PlatformThreadId test_thread_id_ = PlatformThread::CurrentId();
+
+ // This is written to by the test main thread and read from the hang watching
+ // thread. It does not need to be protected because access to it is
+ // synchronized by always setting before triggering the execution of the
+ // reading code through HangWatcher::SignalMonitorEventForTesting().
+ std::string list_of_hung_thread_ids_during_capture_;
+
+ // This is written to by from the hang watching thread and read the test main
+ // thread. It does not need to be protected because access to it is
+ // synchronized by always reading after monitor_event_ has been signaled.
+ int hang_capture_count_ = 0;
+
+ // Increases at the same time as |hang_capture_count_| to test that capture
+ // actually took place.
+ int reference_capture_count_ = 0;
+
+ std::string seconds_since_last_power_resume_crash_key_;
+
+ base::test::ScopedFeatureList feature_list_;
+
+ // Used exclusively for MOCK_TIME.
+ test::SingleThreadTaskEnvironment task_environment_{
+ test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+ HangWatcher hang_watcher_;
+};
+} // namespace
+
+// Verify that the hang capture fails when marking a thread for blocking fails.
+// This simulates a WatchHangsInScope completing between the time the hang
+// was dected and the time it is recorded which would create a non-actionable
+// report.
+TEST_F(HangWatcherSnapshotTest, NonActionableReport) {
+ hang_watcher_.SetOnHangClosureForTesting(
+ base::BindLambdaForTesting([this]() { ++hang_capture_count_; }));
+ hang_watcher_.SetAfterMonitorClosureForTesting(
+ base::BindLambdaForTesting([this]() { monitor_event_.Signal(); }));
+
+ hang_watcher_.Start();
+
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+ {
+ // Start a WatchHangsInScope that expires right away. Ensures that
+ // the first monitor will detect a hang.
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+
+ internal::HangWatchState* current_hang_watch_state =
+ internal::HangWatchState::GetHangWatchStateForCurrentThread();
+
+ // Simulate the deadline changing concurrently during the capture. This
+ // makes the capture fail since marking of the deadline fails.
+ ASSERT_NE(current_hang_watch_state->GetDeadline(),
+ base::TimeTicks::FromInternalValue(kArbitraryDeadline));
+ current_hang_watch_state->GetHangWatchDeadlineForTesting()
+ ->SetSwitchBitsClosureForTesting(
+ base::BindLambdaForTesting([]() { return kArbitraryDeadline; }));
+
+ ExpectNoCapture();
+
+ // Marking failed.
+ ASSERT_FALSE(current_hang_watch_state->IsFlagSet(
+ internal::HangWatchDeadline::Flag::kShouldBlockOnHang));
+
+ current_hang_watch_state->GetHangWatchDeadlineForTesting()
+ ->ResetSwitchBitsClosureForTesting();
+ }
+}
+
+// TODO(crbug.com/1223033): On MAC, the base::PlatformThread::CurrentId(...)
+// should return the system wide IDs. The HungThreadIDs test fails because the
+// reported process ids do not match.
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_HungThreadIDs DISABLED_HungThreadIDs
+#else
+#define MAYBE_HungThreadIDs HungThreadIDs
+#endif
+
+TEST_F(HangWatcherSnapshotTest, MAYBE_HungThreadIDs) {
+ // During hang capture the list of hung threads should be populated.
+ hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([this]() {
+ EXPECT_EQ(hang_watcher_.GrabWatchStateSnapshotForTesting()
+ .PrepareHungThreadListCrashKey(),
+ list_of_hung_thread_ids_during_capture_);
+ ++hang_capture_count_;
+ }));
+
+ // When hang capture is over the list should be empty.
+ hang_watcher_.SetAfterMonitorClosureForTesting(
+ base::BindLambdaForTesting([this]() {
+ monitor_event_.Signal();
+ }));
+
+ hang_watcher_.Start();
+
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ BlockingThread blocking_thread(&monitor_event_, base::TimeDelta{});
+ blocking_thread.StartAndWaitForScopeEntered();
+ {
+ // Ensure the blocking thread entered the scope before the main thread. This
+ // will guarantee an ordering while reporting the list of hung threads.
+ task_environment_.AdvanceClock(kSmallCPUQuantum);
+
+ // Start a WatchHangsInScope that expires right away. Ensures that
+ // the first monitor will detect a hang. This scope will naturally have a
+ // later deadline than the one in |blocking_thread_| since it was created
+ // after.
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+
+ // Hung thread list should contain the id the blocking thread and then the
+ // id of the test main thread since that is the order of increasing
+ // deadline.
+ TestIDList(
+ ConcatenateThreadIds({blocking_thread.GetId(), test_thread_id_}));
+
+ // |expires_instantly| and the scope from |blocking_thread| are still live
+ // but already recorded so should be ignored.
+ ExpectNoCapture();
+
+ // Thread is joinable since we signaled |monitor_event_|. This closes the
+ // scope in |blocking_thread|.
+ blocking_thread.Join();
+
+ // |expires_instantly| is still live but already recorded so should be
+ // ignored.
+ ExpectNoCapture();
+ }
+
+ // All HangWatchScopeEnables are over. There should be no capture.
+ ExpectNoCapture();
+
+ // Once all recorded scopes are over creating a new one and monitoring will
+ // trigger a hang detection.
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ TestIDList(ConcatenateThreadIds({test_thread_id_}));
+}
+
+TEST_F(HangWatcherSnapshotTest, TimeSinceLastSystemPowerResumeCrashKey) {
+ // Override the capture of hangs. Simulate a crash key capture.
+ hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([this]() {
+ ++hang_capture_count_;
+ seconds_since_last_power_resume_crash_key_ =
+ hang_watcher_.GetTimeSinceLastSystemPowerResumeCrashKeyValue();
+ }));
+
+ // When hang capture is over, unblock the main thread.
+ hang_watcher_.SetAfterMonitorClosureForTesting(
+ base::BindLambdaForTesting([this]() { monitor_event_.Signal(); }));
+
+ hang_watcher_.Start();
+
+ // Register the main test thread for hang watching.
+ auto unregister_thread_closure =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ {
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.AdvanceClock(kSmallCPUQuantum);
+
+ TriggerMonitorAndWaitForCompletion();
+ EXPECT_EQ(1, hang_capture_count_);
+ EXPECT_EQ("Never suspended", seconds_since_last_power_resume_crash_key_);
+ }
+
+ {
+ test::ScopedPowerMonitorTestSource power_monitor_source;
+ power_monitor_source.Suspend();
+ task_environment_.AdvanceClock(kSmallCPUQuantum);
+
+ {
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ task_environment_.AdvanceClock(kSmallCPUQuantum);
+ TriggerMonitorAndWaitForCompletion();
+ EXPECT_EQ(2, hang_capture_count_);
+ EXPECT_EQ("Power suspended", seconds_since_last_power_resume_crash_key_);
+ }
+
+ power_monitor_source.Resume();
+ constexpr TimeDelta kAfterResumeTime{base::Seconds(5)};
+ task_environment_.AdvanceClock(kAfterResumeTime);
+
+ {
+ WatchHangsInScope expires_instantly(base::TimeDelta{});
+ TriggerMonitorAndWaitForCompletion();
+ EXPECT_EQ(3, hang_capture_count_);
+ EXPECT_EQ(base::NumberToString(kAfterResumeTime.InSeconds()),
+ seconds_since_last_power_resume_crash_key_);
+ }
+ }
+}
+
+namespace {
+
+// Determines how long the HangWatcher will wait between calls to
+// Monitor(). Choose a low value so that that successive invocations happens
+// fast. This makes tests that wait for monitoring run fast and makes tests that
+// expect no monitoring fail fast.
+const base::TimeDelta kMonitoringPeriod = base::Milliseconds(1);
+
+// Test if and how often the HangWatcher periodically monitors for hangs.
+class HangWatcherPeriodicMonitoringTest : public testing::Test {
+ public:
+ HangWatcherPeriodicMonitoringTest() {
+ hang_watcher_.InitializeOnMainThread(
+ HangWatcher::ProcessType::kBrowserProcess);
+
+ hang_watcher_.SetMonitoringPeriodForTesting(kMonitoringPeriod);
+ hang_watcher_.SetOnHangClosureForTesting(base::BindRepeating(
+ &WaitableEvent::Signal, base::Unretained(&hang_event_)));
+
+ // HangWatcher uses a TickClock to detect how long it slept in between calls
+ // to Monitor(). Override that clock to control its subjective passage of
+ // time.
+ hang_watcher_.SetTickClockForTesting(&test_clock_);
+ }
+
+ HangWatcherPeriodicMonitoringTest(
+ const HangWatcherPeriodicMonitoringTest& other) = delete;
+ HangWatcherPeriodicMonitoringTest& operator=(
+ const HangWatcherPeriodicMonitoringTest& other) = delete;
+
+ void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); }
+
+ protected:
+ // Setup the callback invoked after waiting in HangWatcher to advance the
+ // tick clock by the desired time delta.
+ void InstallAfterWaitCallback(base::TimeDelta time_delta) {
+ hang_watcher_.SetAfterWaitCallbackForTesting(base::BindLambdaForTesting(
+ [this, time_delta](base::TimeTicks time_before_wait) {
+ test_clock_.Advance(time_delta);
+ }));
+ }
+
+ base::SimpleTestTickClock test_clock_;
+
+ // Single threaded to avoid ThreadPool WorkerThreads registering. Will run
+ // delayed tasks created by the tests.
+ test::SingleThreadTaskEnvironment task_environment_;
+
+ std::unique_ptr<base::TickClock> fake_tick_clock_;
+ HangWatcher hang_watcher_;
+
+ // Signaled when a hang is detected.
+ WaitableEvent hang_event_;
+
+ base::ScopedClosureRunner unregister_thread_closure_;
+};
+} // namespace
+
+// Don't register any threads for hang watching. HangWatcher should not monitor.
+TEST_F(HangWatcherPeriodicMonitoringTest,
+ NoPeriodicMonitoringWithoutRegisteredThreads) {
+ RunLoop run_loop;
+
+ // If a call to HangWatcher::Monitor() takes place the test will instantly
+ // fail.
+ hang_watcher_.SetAfterMonitorClosureForTesting(
+ base::BindLambdaForTesting([&run_loop]() {
+ ADD_FAILURE() << "Monitoring took place!";
+ run_loop.Quit();
+ }));
+
+ // Make the HangWatcher tick clock advance by exactly the monitoring period
+ // after waiting so it will never detect oversleeping between attempts to call
+ // Monitor(). This would inhibit monitoring and make the test pass for the
+ // wrong reasons.
+ InstallAfterWaitCallback(kMonitoringPeriod);
+
+ hang_watcher_.Start();
+
+ // Unblock the test thread. No thread ever registered after the HangWatcher
+ // was created in the test's constructor. No monitoring should have taken
+ // place.
+ task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+
+ // NOTE:
+ // A lack of calls could technically also be caused by the HangWatcher thread
+ // executing too slowly / being descheduled. This is a known limitation.
+ // It's expected for |TestTimeouts::tiny_timeout()| to be large enough that
+ // this is rare.
+}
+
+// During normal execution periodic monitorings should take place.
+TEST_F(HangWatcherPeriodicMonitoringTest, PeriodicCallsTakePlace) {
+ // HangWatcher::Monitor() will run once right away on thread registration.
+ // We want to make sure it runs at a couple more times from being scheduled.
+ constexpr int kMinimumMonitorCount = 3;
+
+ RunLoop run_loop;
+
+ // Setup the HangWatcher to unblock run_loop when the Monitor() has been
+ // invoked enough times.
+ hang_watcher_.SetAfterMonitorClosureForTesting(BarrierClosure(
+ kMinimumMonitorCount, base::BindLambdaForTesting([&run_loop]() {
+ // Test condition are confirmed, stop monitoring.
+ HangWatcher::StopMonitoringForTesting();
+
+ // Unblock the test main thread.
+ run_loop.Quit();
+ })));
+
+ // Make the HangWatcher tick clock advance by exactly the monitoring period
+ // after waiting so it will never detect oversleeping between attempts to call
+ // Monitor(). This would inhibit monitoring.
+ InstallAfterWaitCallback(kMonitoringPeriod);
+
+ hang_watcher_.Start();
+
+ // Register a thread,
+ unregister_thread_closure_ =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ run_loop.Run();
+
+ // No monitored scope means no possible hangs.
+ ASSERT_FALSE(hang_event_.IsSignaled());
+}
+
+// If the HangWatcher detects it slept for longer than expected it will not
+// monitor.
+TEST_F(HangWatcherPeriodicMonitoringTest, NoMonitorOnOverSleep) {
+ RunLoop run_loop;
+
+ // If a call to HangWatcher::Monitor() takes place the test will instantly
+ // fail.
+ hang_watcher_.SetAfterMonitorClosureForTesting(
+ base::BindLambdaForTesting([&run_loop]() {
+ ADD_FAILURE() << "Monitoring took place!";
+ run_loop.Quit();
+ }));
+
+ // Make the HangWatcher tick clock advance so much after waiting that it will
+ // detect oversleeping every time. This will keep it from monitoring.
+ InstallAfterWaitCallback(base::Minutes(1));
+
+ hang_watcher_.Start();
+
+ // Register a thread.
+ unregister_thread_closure_ =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+
+ // Unblock the test thread. All waits were perceived as oversleeping so all
+ // monitoring was inhibited.
+ task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+
+ // NOTE: A lack of calls could technically also be caused by the HangWatcher
+ // thread executing too slowly / being descheduled. This is a known
+ // limitation. It's expected for |TestTimeouts::tiny_timeout()| to be large
+ // enough that this happens rarely.
+}
+
+namespace {
+class WatchHangsInScopeBlockingTest : public testing::Test {
+ public:
+ WatchHangsInScopeBlockingTest() {
+ feature_list_.InitWithFeaturesAndParameters(kFeatureAndParams, {});
+ hang_watcher_.InitializeOnMainThread(
+ HangWatcher::ProcessType::kBrowserProcess);
+
+ hang_watcher_.SetOnHangClosureForTesting(base::BindLambdaForTesting([&] {
+ capture_started_.Signal();
+ // Simulate capturing that takes a long time.
+ PlatformThread::Sleep(base::Milliseconds(500));
+
+ continue_capture_.Wait();
+ completed_capture_ = true;
+ }));
+
+ hang_watcher_.SetAfterMonitorClosureForTesting(
+ base::BindLambdaForTesting([&] {
+ // Simulate monitoring that takes a long time.
+ PlatformThread::Sleep(base::Milliseconds(500));
+ completed_monitoring_.Signal();
+ }));
+
+ // Make sure no periodic monitoring takes place.
+ hang_watcher_.SetMonitoringPeriodForTesting(kVeryLongDelta);
+
+ hang_watcher_.Start();
+
+ // Register the test main thread for hang watching.
+ unregister_thread_closure_ =
+ HangWatcher::RegisterThread(base::HangWatcher::ThreadType::kMainThread);
+ }
+
+ void TearDown() override { hang_watcher_.UnitializeOnMainThreadForTesting(); }
+
+ WatchHangsInScopeBlockingTest(const WatchHangsInScopeBlockingTest& other) =
+ delete;
+ WatchHangsInScopeBlockingTest& operator=(
+ const WatchHangsInScopeBlockingTest& other) = delete;
+
+ void VerifyScopesDontBlock() {
+ // Start a WatchHangsInScope that cannot possibly cause a hang to be
+ // detected.
+ {
+ WatchHangsInScope long_scope(kVeryLongDelta);
+
+ // Manually trigger a monitoring.
+ hang_watcher_.SignalMonitorEventForTesting();
+
+ // Execution has to continue freely here as no capture is in progress.
+ }
+
+ // Monitoring should not be over yet because the test code should execute
+ // faster when not blocked.
+ EXPECT_FALSE(completed_monitoring_.IsSignaled());
+
+ // Wait for the full monitoring process to be complete. This is to prove
+ // that monitoring truly executed and that we raced the signaling.
+ completed_monitoring_.Wait();
+
+ // No hang means no capture.
+ EXPECT_FALSE(completed_capture_);
+ }
+
+ protected:
+ base::WaitableEvent capture_started_;
+ base::WaitableEvent completed_monitoring_;
+
+ // The HangWatcher waits on this event via the "on hang" closure when a hang
+ // is detected.
+ base::WaitableEvent continue_capture_;
+ bool completed_capture_{false};
+
+ base::test::ScopedFeatureList feature_list_;
+ HangWatcher hang_watcher_;
+ base::ScopedClosureRunner unregister_thread_closure_;
+};
+} // namespace
+
+// Tests that execution is unimpeded by ~WatchHangsInScope() when no capture
+// ever takes place.
+TEST_F(WatchHangsInScopeBlockingTest, ScopeDoesNotBlocksWithoutCapture) {
+ // No capture should take place so |continue_capture_| is not signaled to
+ // create a test hang if one ever does.
+ VerifyScopesDontBlock();
+}
+
+// Test that execution blocks in ~WatchHangsInScope() for a thread under
+// watch during the capturing of a hang.
+TEST_F(WatchHangsInScopeBlockingTest, ScopeBlocksDuringCapture) {
+ // The capture completing is not dependent on any test event. Signal to make
+ // sure the test is not blocked.
+ continue_capture_.Signal();
+
+ // Start a WatchHangsInScope that expires in the past already. Ensures
+ // that the first monitor will detect a hang.
+ {
+ // Start a WatchHangsInScope that expires right away. Ensures that the
+ // first monitor will detect a hang.
+ WatchHangsInScope expires_right_away(base::TimeDelta{});
+
+ // Manually trigger a monitoring.
+ hang_watcher_.SignalMonitorEventForTesting();
+
+ // Ensure that the hang capturing started.
+ capture_started_.Wait();
+
+ // Execution will get stuck in the outer scope because it can't escape
+ // ~WatchHangsInScope() if a hang capture is under way.
+ }
+
+ // A hang was in progress so execution should have been blocked in
+ // BlockWhileCaptureInProgress() until capture finishes.
+ EXPECT_TRUE(completed_capture_);
+ completed_monitoring_.Wait();
+
+ // Reset expectations
+ completed_monitoring_.Reset();
+ capture_started_.Reset();
+ completed_capture_ = false;
+
+ // Verify that scopes don't block just because a capture happened in the past.
+ VerifyScopesDontBlock();
+}
+
+#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
+// Flaky hangs on arm64 Macs: https://crbug.com/1140207
+#define MAYBE_NewScopeDoesNotBlockDuringCapture \
+ DISABLED_NewScopeDoesNotBlockDuringCapture
+#else
+#define MAYBE_NewScopeDoesNotBlockDuringCapture \
+ NewScopeDoesNotBlockDuringCapture
+#endif
+
+// Test that execution does not block in ~WatchHangsInScope() when the scope
+// was created after the start of a capture.
+TEST_F(WatchHangsInScopeBlockingTest, MAYBE_NewScopeDoesNotBlockDuringCapture) {
+ // Start a WatchHangsInScope that expires right away. Ensures that the
+ // first monitor will detect a hang.
+ WatchHangsInScope expires_right_away(base::TimeDelta{});
+
+ // Manually trigger a monitoring.
+ hang_watcher_.SignalMonitorEventForTesting();
+
+ // Ensure that the hang capturing started.
+ capture_started_.Wait();
+
+ // A scope started once a capture is already under way should not block
+ // execution.
+ { WatchHangsInScope also_expires_right_away(base::TimeDelta{}); }
+
+ // Wait for the new WatchHangsInScope to be destroyed to let the capture
+ // finish. If the new scope block waiting for the capture to finish this would
+ // create a deadlock and the test would hang.
+ continue_capture_.Signal();
+}
+
+namespace internal {
+namespace {
+
+constexpr std::array<HangWatchDeadline::Flag, 3> kAllFlags{
+ {HangWatchDeadline::Flag::kMinValue,
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope,
+ HangWatchDeadline::Flag::kShouldBlockOnHang}};
+} // namespace
+
+class HangWatchDeadlineTest : public testing::Test {
+ protected:
+ void AssertNoFlagsSet() const {
+ for (HangWatchDeadline::Flag flag : kAllFlags) {
+ ASSERT_FALSE(deadline_.IsFlagSet(flag));
+ }
+ }
+
+ // Return a flag mask without one of the flags for test purposes. Use to
+ // ignore that effect of setting a flag that was just set.
+ uint64_t FlagsMinus(uint64_t flags, HangWatchDeadline::Flag flag) {
+ return flags & ~(static_cast<uint64_t>(flag));
+ }
+
+ HangWatchDeadline deadline_;
+};
+
+// Verify that the extract functions don't mangle any bits.
+TEST_F(HangWatchDeadlineTest, BitsPreservedThroughExtract) {
+ for (auto bits : {kAllOnes, kAllZeros, kOnesThenZeroes, kZeroesThenOnes}) {
+ ASSERT_TRUE((HangWatchDeadline::ExtractFlags(bits) |
+ HangWatchDeadline::ExtractDeadline(bits)) == bits);
+ }
+}
+
+// Verify that setting and clearing a persistent flag works and has no unwanted
+// side-effects. Neither the flags nor the deadline change concurrently in this
+// test.
+TEST_F(HangWatchDeadlineTest, SetAndClearPersistentFlag) {
+ AssertNoFlagsSet();
+
+ // Grab the original values for flags and deadline.
+ auto [old_flags, old_deadline] = deadline_.GetFlagsAndDeadline();
+
+ // Set the flag. Operation cannot fail.
+ deadline_.SetIgnoreCurrentWatchHangsInScope();
+
+ // Get new flags and deadline.
+ auto [new_flags, new_deadline] = deadline_.GetFlagsAndDeadline();
+
+ // Flag was set properly.
+ ASSERT_TRUE(HangWatchDeadline::IsFlagSet(
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, new_flags));
+
+ // No side-effect on deadline.
+ ASSERT_EQ(new_deadline, old_deadline);
+
+ // No side-effect on other flags.
+ ASSERT_EQ(
+ FlagsMinus(new_flags,
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope),
+ old_flags);
+
+ // Clear the flag, operation cannot fail.
+ deadline_.UnsetIgnoreCurrentWatchHangsInScope();
+
+ // Update new values.
+ std::tie(new_flags, new_deadline) = deadline_.GetFlagsAndDeadline();
+
+ // All flags back to original state.
+ ASSERT_EQ(new_flags, old_flags);
+
+ // Deadline still unnafected.
+ ASSERT_EQ(new_deadline, old_deadline);
+}
+
+// Verify setting the TimeTicks value works and has no unwanted side-effects.
+TEST_F(HangWatchDeadlineTest, SetDeadline) {
+ TimeTicks ticks;
+
+ AssertNoFlagsSet();
+ ASSERT_NE(deadline_.GetDeadline(), ticks);
+
+ // Set the deadline and verify it stuck.
+ deadline_.SetDeadline(ticks);
+ ASSERT_EQ(deadline_.GetDeadline(), ticks);
+
+ // Only the value was modified, no flags should be set.
+ AssertNoFlagsSet();
+}
+
+// Verify that setting a non-persistent flag (kShouldBlockOnHang)
+// when the TimeTicks value changed since calling the flag setting
+// function fails and has no side-effects.
+TEST_F(HangWatchDeadlineTest, SetShouldBlockOnHangDeadlineChanged) {
+ AssertNoFlagsSet();
+
+ auto [flags, deadline] = deadline_.GetFlagsAndDeadline();
+
+ // Simulate value change. Flags are constant.
+ const base::TimeTicks new_deadline =
+ base::TimeTicks::FromInternalValue(kArbitraryDeadline);
+ ASSERT_NE(deadline, new_deadline);
+ deadline_.SetSwitchBitsClosureForTesting(
+ base::BindLambdaForTesting([]() { return kArbitraryDeadline; }));
+
+ // kShouldBlockOnHangs does not persist through value change.
+ ASSERT_FALSE(deadline_.SetShouldBlockOnHang(flags, deadline));
+
+ // Flag was not applied.
+ ASSERT_FALSE(
+ deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
+
+ // New value that was changed concurrently is preserved.
+ ASSERT_EQ(deadline_.GetDeadline(), new_deadline);
+}
+
+// Verify that clearing a persistent (kIgnoreCurrentWatchHangsInScope) when
+// the value changed succeeds and has non side-effects.
+TEST_F(HangWatchDeadlineTest, ClearIgnoreHangsDeadlineChanged) {
+ AssertNoFlagsSet();
+
+ auto [flags, deadline] = deadline_.GetFlagsAndDeadline();
+
+ deadline_.SetIgnoreCurrentWatchHangsInScope();
+ std::tie(flags, deadline) = deadline_.GetFlagsAndDeadline();
+ ASSERT_TRUE(HangWatchDeadline::IsFlagSet(
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope, flags));
+
+ // Simulate deadline change. Flags are constant.
+ const base::TimeTicks new_deadline =
+ base::TimeTicks::FromInternalValue(kArbitraryDeadline);
+ ASSERT_NE(deadline, new_deadline);
+ deadline_.SetSwitchBitsClosureForTesting(base::BindLambdaForTesting([]() {
+ return static_cast<uint64_t>(HangWatchDeadline::Flag::kShouldBlockOnHang) |
+ kArbitraryDeadline;
+ }));
+
+ // Clearing kIgnoreHang is unaffected by deadline or flags change.
+ deadline_.UnsetIgnoreCurrentWatchHangsInScope();
+ ASSERT_FALSE(deadline_.IsFlagSet(
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
+
+ // New deadline that was changed concurrently is preserved.
+ ASSERT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
+ ASSERT_EQ(deadline_.GetDeadline(), new_deadline);
+}
+
+// Verify that setting a persistent (kIgnoreCurrentWatchHangsInScope) when
+// the deadline or flags changed succeeds and has non side-effects.
+TEST_F(HangWatchDeadlineTest,
+ SetIgnoreCurrentHangWatchScopeEnableDeadlineChangedd) {
+ AssertNoFlagsSet();
+
+ auto [flags, deadline] = deadline_.GetFlagsAndDeadline();
+
+ // Simulate deadline change. Flags are constant.
+ const base::TimeTicks new_deadline =
+ base::TimeTicks::FromInternalValue(kArbitraryDeadline);
+
+ ASSERT_NE(deadline, new_deadline);
+ deadline_.SetSwitchBitsClosureForTesting(base::BindLambdaForTesting([]() {
+ return static_cast<uint64_t>(HangWatchDeadline::Flag::kShouldBlockOnHang) |
+ kArbitraryDeadline;
+ }));
+
+ // kIgnoreHang persists through value change.
+ deadline_.SetIgnoreCurrentWatchHangsInScope();
+ ASSERT_TRUE(deadline_.IsFlagSet(
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
+
+ // New deadline and flags that changed concurrently are preserved.
+ ASSERT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
+ ASSERT_EQ(deadline_.GetDeadline(), new_deadline);
+}
+
+// Setting a new deadline should wipe flags that a not persistent.
+// Persistent flags should not be disturbed.
+TEST_F(HangWatchDeadlineTest, SetDeadlineWipesFlags) {
+ auto [flags, deadline] = deadline_.GetFlagsAndDeadline();
+
+ ASSERT_TRUE(deadline_.SetShouldBlockOnHang(flags, deadline));
+ ASSERT_TRUE(deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
+
+ std::tie(flags, deadline) = deadline_.GetFlagsAndDeadline();
+
+ deadline_.SetIgnoreCurrentWatchHangsInScope();
+ ASSERT_TRUE(deadline_.IsFlagSet(
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
+
+ // Change the deadline.
+ deadline_.SetDeadline(TimeTicks{});
+ ASSERT_EQ(deadline_.GetDeadline(), TimeTicks{});
+
+ // Verify the persistent flag stuck and the non-persistent one was unset.
+ ASSERT_FALSE(
+ deadline_.IsFlagSet(HangWatchDeadline::Flag::kShouldBlockOnHang));
+ ASSERT_TRUE(deadline_.IsFlagSet(
+ HangWatchDeadline::Flag::kIgnoreCurrentWatchHangsInScope));
+}
+
+} // namespace internal
+
+} // namespace base
diff --git a/base/threading/platform_thread.cc b/base/threading/platform_thread.cc
new file mode 100644
index 0000000..33abed1
--- /dev/null
+++ b/base/threading/platform_thread.cc
@@ -0,0 +1,157 @@
+// Copyright 2018 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/platform_thread.h"
+
+#include "base/task/current_thread.h"
+#include "third_party/abseil-cpp/absl/base/attributes.h"
+
+#if BUILDFLAG(IS_FUCHSIA)
+#include "base/fuchsia/scheduler.h"
+#endif
+
+#if defined(STARBOARD)
+#include <pthread.h>
+
+#include "base/check_op.h"
+#endif
+
+namespace base {
+
+namespace {
+
+#if defined(STARBOARD)
+
+enum TlsValue {
+ kDefault = 0,
+ kBackground = 1,
+ kUtility = 2,
+ kResourceEfficient = 3 ,
+ kCompositing = 4,
+ kDisplayCritical = 5,
+ kRealtimeAudio = 6,
+ kMaxValue = kRealtimeAudio,
+};
+
+TlsValue ThreadTypeToTlsValue(ThreadType type) {
+ if (type == ThreadType::kMaxValue) {
+ type = ThreadType::kRealtimeAudio;
+ }
+ switch(type) {
+ case ThreadType::kDefault:
+ return TlsValue::kDefault;
+ case ThreadType::kBackground:
+ return TlsValue::kBackground;
+ case ThreadType::kUtility:
+ return TlsValue::kUtility;
+ case ThreadType::kResourceEfficient:
+ return TlsValue::kResourceEfficient;
+ case ThreadType::kCompositing:
+ return TlsValue::kCompositing;
+ case ThreadType::kDisplayCritical:
+ return TlsValue::kDisplayCritical;
+ case ThreadType::kRealtimeAudio:
+ return TlsValue::kRealtimeAudio;
+ }
+}
+
+ThreadType TlsValueToThreadType(TlsValue tls_value) {
+ if (tls_value == TlsValue::kMaxValue) {
+ tls_value = TlsValue::kRealtimeAudio;
+ }
+ switch(tls_value) {
+ case TlsValue::kDefault:
+ return ThreadType::kDefault;
+ case TlsValue::kBackground:
+ return ThreadType::kBackground;
+ case TlsValue::kUtility:
+ return ThreadType::kUtility;
+ case TlsValue::kResourceEfficient:
+ return ThreadType::kResourceEfficient;
+ case TlsValue::kCompositing:
+ return ThreadType::kCompositing;
+ case TlsValue::kDisplayCritical:
+ return ThreadType::kDisplayCritical;
+ case TlsValue::kRealtimeAudio:
+ return ThreadType::kRealtimeAudio;
+ }
+}
+
+ABSL_CONST_INIT pthread_once_t s_once_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_key = 0;
+
+void InitThreadLocalKey() {
+ int res = pthread_key_create(&s_thread_local_key , NULL);
+ DCHECK(res == 0);
+}
+
+void EnsureThreadLocalKeyInited() {
+ pthread_once(&s_once_flag, InitThreadLocalKey);
+}
+
+ThreadType GetCurrentThreadTypeValue() {
+ EnsureThreadLocalKeyInited();
+ return TlsValueToThreadType(TlsValue(reinterpret_cast<intptr_t>(pthread_getspecific(s_thread_local_key))));
+}
+#else
+ABSL_CONST_INIT thread_local ThreadType current_thread_type =
+ ThreadType::kDefault;
+#endif
+
+} // namespace
+
+// static
+void PlatformThread::SetCurrentThreadType(ThreadType thread_type) {
+ MessagePumpType message_pump_type = MessagePumpType::DEFAULT;
+ if (CurrentIOThread::IsSet()) {
+ message_pump_type = MessagePumpType::IO;
+ }
+#if !BUILDFLAG(IS_NACL)
+ else if (CurrentUIThread::IsSet()) {
+ message_pump_type = MessagePumpType::UI;
+ }
+#endif
+ internal::SetCurrentThreadType(thread_type, message_pump_type);
+}
+
+// static
+ThreadType PlatformThread::GetCurrentThreadType() {
+#if defined(STARBOARD)
+ ThreadType type = GetCurrentThreadTypeValue();
+ return type;
+#else
+ return current_thread_type;
+#endif
+}
+
+// static
+absl::optional<TimeDelta> PlatformThread::GetThreadLeewayOverride() {
+#if BUILDFLAG(IS_FUCHSIA)
+ // On Fuchsia, all audio threads run with the CPU scheduling profile that uses
+ // an interval of |kAudioSchedulingPeriod|. Using the default leeway may lead
+ // to some tasks posted to audio threads to be executed too late (see
+ // http://crbug.com/1368858).
+ if (GetCurrentThreadType() == ThreadType::kRealtimeAudio)
+ return kAudioSchedulingPeriod;
+#endif
+ return absl::nullopt;
+}
+
+namespace internal {
+
+void SetCurrentThreadType(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
+ CHECK_LE(thread_type, ThreadType::kMaxValue);
+ SetCurrentThreadTypeImpl(thread_type, pump_type_hint);
+#if defined(STARBOARD)
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, reinterpret_cast<void*>(ThreadTypeToTlsValue(thread_type)));
+#else
+ current_thread_type = thread_type;
+#endif
+}
+
+} // namespace internal
+
+} // namespace base
diff --git a/base/threading/platform_thread.h b/base/threading/platform_thread.h
index 1882df8..0623f59 100644
--- a/base/threading/platform_thread.h
+++ b/base/threading/platform_thread.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -9,26 +9,30 @@
#ifndef BASE_THREADING_PLATFORM_THREAD_H_
#define BASE_THREADING_PLATFORM_THREAD_H_
+#include <stddef.h>
+
+#include <iosfwd>
+#include <type_traits>
+
#include "base/base_export.h"
-#include "base/macros.h"
+#include "base/message_loop/message_pump_type.h"
+#include "base/threading/platform_thread_ref.h"
#include "base/time/time.h"
#include "build/build_config.h"
+#include "build/chromeos_buildflags.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
#if defined(STARBOARD)
#include "starboard/thread.h"
-#else
-#if defined(OS_WIN)
+#elif BUILDFLAG(IS_WIN)
#include "base/win/windows_types.h"
-#elif defined(OS_FUCHSIA)
+#elif BUILDFLAG(IS_FUCHSIA)
#include <zircon/types.h>
-#elif defined(OS_MACOSX)
+#elif BUILDFLAG(IS_APPLE)
#include <mach/mach_types.h>
-#elif defined(OS_POSIX)
+#elif BUILDFLAG(IS_POSIX)
#include <pthread.h>
#include <unistd.h>
-
-#include "starboard/types.h"
-#endif
#endif
namespace base {
@@ -36,65 +40,30 @@
// Used for logging. Always an integer value.
#if defined(STARBOARD)
typedef SbThreadId PlatformThreadId;
-#else
-#if defined(OS_WIN)
+#elif BUILDFLAG(IS_WIN)
typedef DWORD PlatformThreadId;
-#elif defined(OS_FUCHSIA)
+#elif BUILDFLAG(IS_FUCHSIA)
typedef zx_handle_t PlatformThreadId;
-#elif defined(OS_MACOSX)
+#elif BUILDFLAG(IS_APPLE)
typedef mach_port_t PlatformThreadId;
-#elif defined(OS_POSIX)
+#elif BUILDFLAG(IS_POSIX)
typedef pid_t PlatformThreadId;
#endif
-#endif
-
-// Used for thread checking and debugging.
-// Meant to be as fast as possible.
-// These are produced by PlatformThread::CurrentRef(), and used to later
-// check if we are on the same thread or not by using ==. These are safe
-// to copy between threads, but can't be copied to another process as they
-// have no meaning there. Also, the internal identifier can be re-used
-// after a thread dies, so a PlatformThreadRef cannot be reliably used
-// to distinguish a new thread from an old, dead thread.
-class PlatformThreadRef {
- public:
-#if defined(STARBOARD)
- typedef SbThread RefType;
-#else
-#if defined(OS_WIN)
- typedef DWORD RefType;
-#else // OS_POSIX
- typedef pthread_t RefType;
-#endif
-#endif
- constexpr PlatformThreadRef() : id_(0) {}
-
- explicit constexpr PlatformThreadRef(RefType id) : id_(id) {}
-
- bool operator==(PlatformThreadRef other) const {
- return id_ == other.id_;
- }
-
- bool operator!=(PlatformThreadRef other) const { return id_ != other.id_; }
-
- bool is_null() const {
- return id_ == 0;
- }
- private:
- RefType id_;
-};
+static_assert(std::is_integral_v<PlatformThreadId>, "Always an integer value.");
// Used to operate on threads.
class PlatformThreadHandle {
public:
#if defined(STARBOARD)
+#if SB_API_VERSION < 16
typedef SbThread Handle;
#else
-#if defined(OS_WIN)
- typedef void* Handle;
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
typedef pthread_t Handle;
#endif
+#elif BUILDFLAG(IS_WIN)
+ typedef void* Handle;
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+ typedef pthread_t Handle;
#endif
constexpr PlatformThreadHandle() : handle_(0) {}
@@ -119,29 +88,64 @@
const PlatformThreadId kInvalidThreadId(0);
-// Valid values for priority of Thread::Options and SimpleThread::Options, and
-// SetCurrentThreadPriority(), listed in increasing order of importance.
-enum class ThreadPriority : int {
-#if defined(STARBOARD)
- DEFAULT = kSbThreadNoPriority,
- LOWEST = kSbThreadPriorityLowest,
- BACKGROUND = kSbThreadPriorityLow,
- NORMAL = kSbThreadPriorityNormal,
- DISPLAY = kSbThreadPriorityHigh,
- HIGHEST = kSbThreadPriorityHighest,
- REALTIME_AUDIO = kSbThreadPriorityRealTime, // Could be equal to HIGHEST.
-#else
- // Suitable for threads that shouldn't disrupt high priority work.
- BACKGROUND,
- // Default priority level.
- NORMAL,
- // Suitable for threads which generate data for the display (at ~60Hz).
- DISPLAY,
+// Valid values for `thread_type` of Thread::Options, SimpleThread::Options,
+// and SetCurrentThreadType(), listed in increasing order of importance.
+//
+// It is up to each platform-specific implementation what these translate to.
+// Callers should avoid setting different ThreadTypes on different platforms
+// (ifdefs) at all cost, instead the platform differences should be encoded in
+// the platform-specific implementations. Some implementations may treat
+// adjacent ThreadTypes in this enum as equivalent.
+//
+// Reach out to //base/task/OWNERS (scheduler-dev@chromium.org) before changing
+// thread type assignments in your component, as such decisions affect the whole
+// of Chrome.
+//
+// Refer to PlatformThreadTest.SetCurrentThreadTypeTest in
+// platform_thread_unittest.cc for the most up-to-date state of each platform's
+// handling of ThreadType.
+enum class ThreadType : int {
+ // Suitable for threads that have the least urgency and lowest priority, and
+ // can be interrupted or delayed by other types.
+ kBackground,
+ // Suitable for threads that are less important than normal type, and can be
+ // interrupted or delayed by threads with kDefault type.
+ kUtility,
+ // Suitable for threads that produce user-visible artifacts but aren't
+ // latency sensitive. The underlying platform will try to be economic
+ // in its usage of resources for this thread, if possible.
+ kResourceEfficient,
+ // Default type. The thread priority or quality of service will be set to
+ // platform default. In Chrome, this is suitable for handling user
+ // interactions (input), only display and audio can get a higher priority.
+ kDefault,
+ // Suitable for threads which are critical to compositing the foreground
+ // content.
+ kCompositing,
+ // Suitable for display critical threads.
+ kDisplayCritical,
// Suitable for low-latency, glitch-resistant audio.
- REALTIME_AUDIO,
-#endif
+ kRealtimeAudio,
+ kMaxValue = kRealtimeAudio,
};
+// Cross-platform mapping of physical thread priorities. Used by tests to verify
+// the underlying effects of SetCurrentThreadType.
+enum class ThreadPriorityForTest : int {
+ kBackground,
+ kUtility,
+ kNormal,
+ // The priority obtained via ThreadType::kDisplayCritical (and potentially
+ // other ThreadTypes).
+ kDisplay,
+ kRealtimeAudio,
+ kMaxValue = kRealtimeAudio,
+};
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+class ThreadTypeDelegate;
+#endif
+
// A namespace for low-level thread functions.
class BASE_EXPORT PlatformThread {
public:
@@ -149,12 +153,24 @@
// ThreadMain method will be called on the newly created thread.
class BASE_EXPORT Delegate {
public:
+#if BUILDFLAG(IS_APPLE)
+ // The interval at which the thread expects to have work to do. Zero if
+ // unknown. (Example: audio buffer duration for real-time audio.) Is used to
+ // optimize the thread real-time behavior. Is called on the newly created
+ // thread before ThreadMain().
+ virtual TimeDelta GetRealtimePeriod();
+#endif
+
virtual void ThreadMain() = 0;
protected:
virtual ~Delegate() = default;
};
+ PlatformThread() = delete;
+ PlatformThread(const PlatformThread&) = delete;
+ PlatformThread& operator=(const PlatformThread&) = delete;
+
// Gets the current thread id, which may be useful for logging purposes.
static PlatformThreadId CurrentId();
@@ -169,9 +185,19 @@
static PlatformThreadHandle CurrentHandle();
// Yield the current thread so another thread can be scheduled.
+ //
+ // Note: this is likely not the right call to make in most situations. If this
+ // is part of a spin loop, consider base::Lock, which likely has better tail
+ // latency. Yielding the thread has different effects depending on the
+ // platform, system load, etc., and can result in yielding the CPU for less
+ // than 1us, or many tens of ms.
static void YieldCurrentThread();
- // Sleeps for the specified duration.
+ // Sleeps for the specified duration (real-time; ignores time overrides).
+ // Note: The sleep duration may be in base::Time or base::TimeTicks, depending
+ // on platform. If you're looking to use this in unit tests testing delayed
+ // tasks, this will be unreliable - instead, use
+ // base::test::TaskEnvironment with MOCK_TIME mode.
static void Sleep(base::TimeDelta duration);
// Sets the thread name visible to debuggers/tools. This will try to
@@ -181,10 +207,10 @@
// Gets the thread name, if previously set by SetName.
static const char* GetName();
- // Creates a new thread. The |stack_size| parameter can be 0 to indicate
+ // Creates a new thread. The `stack_size` parameter can be 0 to indicate
// that the default stack size should be used. Upon success,
- // |*thread_handle| will be assigned a handle to the newly created thread,
- // and |delegate|'s ThreadMain method will be executed on the newly created
+ // `*thread_handle` will be assigned a handle to the newly created thread,
+ // and `delegate`'s ThreadMain method will be executed on the newly created
// thread.
// NOTE: When you are done with the thread handle, you must call Join to
// release system resources associated with the thread. You must ensure that
@@ -192,75 +218,114 @@
static bool Create(size_t stack_size,
Delegate* delegate,
PlatformThreadHandle* thread_handle) {
- return CreateWithPriority(stack_size, delegate, thread_handle,
- ThreadPriority::NORMAL);
+ return CreateWithType(stack_size, delegate, thread_handle,
+ ThreadType::kDefault);
}
- // CreateWithPriority() does the same thing as Create() except the priority of
- // the thread is set based on |priority|.
- static bool CreateWithPriority(size_t stack_size, Delegate* delegate,
- PlatformThreadHandle* thread_handle,
- ThreadPriority priority);
+ // CreateWithType() does the same thing as Create() except the priority and
+ // possibly the QoS of the thread is set based on `thread_type`.
+ // `pump_type_hint` must be provided if the thread will be using a
+ // MessagePumpForUI or MessagePumpForIO as this affects the application of
+ // `thread_type`.
+ static bool CreateWithType(
+ size_t stack_size,
+ Delegate* delegate,
+ PlatformThreadHandle* thread_handle,
+ ThreadType thread_type,
+ MessagePumpType pump_type_hint = MessagePumpType::DEFAULT);
// CreateNonJoinable() does the same thing as Create() except the thread
// cannot be Join()'d. Therefore, it also does not output a
// PlatformThreadHandle.
static bool CreateNonJoinable(size_t stack_size, Delegate* delegate);
- // CreateNonJoinableWithPriority() does the same thing as CreateNonJoinable()
- // except the priority of the thread is set based on |priority|.
- static bool CreateNonJoinableWithPriority(size_t stack_size,
- Delegate* delegate,
- ThreadPriority priority);
+ // CreateNonJoinableWithType() does the same thing as CreateNonJoinable()
+ // except the type of the thread is set based on `type`. `pump_type_hint` must
+ // be provided if the thread will be using a MessagePumpForUI or
+ // MessagePumpForIO as this affects the application of `thread_type`.
+ static bool CreateNonJoinableWithType(
+ size_t stack_size,
+ Delegate* delegate,
+ ThreadType thread_type,
+ MessagePumpType pump_type_hint = MessagePumpType::DEFAULT);
// Joins with a thread created via the Create function. This function blocks
// the caller until the designated thread exits. This will invalidate
- // |thread_handle|.
+ // `thread_handle`.
static void Join(PlatformThreadHandle thread_handle);
// Detaches and releases the thread handle. The thread is no longer joinable
- // and |thread_handle| is invalidated after this call.
+ // and `thread_handle` is invalidated after this call.
static void Detach(PlatformThreadHandle thread_handle);
- // Returns true if SetCurrentThreadPriority() should be able to increase the
- // priority of a thread to |priority|.
- static bool CanIncreaseThreadPriority(ThreadPriority priority);
+ // Returns true if SetCurrentThreadType() should be able to change the type
+ // of a thread in current process from `from` to `to`.
+ static bool CanChangeThreadType(ThreadType from, ThreadType to);
- // Toggles the current thread's priority at runtime.
- //
- // A thread may not be able to raise its priority back up after lowering it if
- // the process does not have a proper permission, e.g. CAP_SYS_NICE on Linux.
- // A thread may not be able to lower its priority back down after raising it
- // to REALTIME_AUDIO.
- //
- // This function must not be called from the main thread on Mac. This is to
- // avoid performance regressions (https://crbug.com/601270).
- //
- // Since changing other threads' priority is not permitted in favor of
- // security, this interface is restricted to change only the current thread
- // priority (https://crbug.com/399473).
- static void SetCurrentThreadPriority(ThreadPriority priority);
+ // Declares the type of work running on the current thread. This will affect
+ // things like thread priority and thread QoS (Quality of Service) to the best
+ // of the current platform's abilities.
+ static void SetCurrentThreadType(ThreadType thread_type);
- static ThreadPriority GetCurrentThreadPriority();
+ // Get the last `thread_type` set by SetCurrentThreadType, no matter if the
+ // underlying priority successfully changed or not.
+ static ThreadType GetCurrentThreadType();
-#if defined(OS_LINUX)
- // Toggles a specific thread's priority at runtime. This can be used to
+ // Returns a realtime period provided by `delegate`.
+ static TimeDelta GetRealtimePeriod(Delegate* delegate);
+
+ // Returns the override of task leeway if any.
+ static absl::optional<TimeDelta> GetThreadLeewayOverride();
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ // Sets a delegate which handles thread type changes for this process. This
+ // must be externally synchronized with any call to SetCurrentThreadType.
+ static void SetThreadTypeDelegate(ThreadTypeDelegate* delegate);
+
+ // Toggles a specific thread's type at runtime. This can be used to
// change the priority of a thread in a different process and will fail
// if the calling process does not have proper permissions. The
- // SetCurrentThreadPriority() function above is preferred in favor of
+ // SetCurrentThreadType() function above is preferred in favor of
// security but on platforms where sandboxed processes are not allowed to
// change priority this function exists to allow a non-sandboxed process
// to change the priority of sandboxed threads for improved performance.
// Warning: Don't use this for a main thread because that will change the
// whole thread group's (i.e. process) priority.
- static void SetThreadPriority(PlatformThreadId thread_id,
- ThreadPriority priority);
+ static void SetThreadType(PlatformThreadId process_id,
+ PlatformThreadId thread_id,
+ ThreadType thread_type);
#endif
- private:
- DISALLOW_IMPLICIT_CONSTRUCTORS(PlatformThread);
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_APPLE)
+ // Signals that the feature list has been initialized which allows to check
+ // the feature's value now and initialize state. This prevents race
+ // conditions where the feature is being checked while it is being
+ // initialized, which can cause a crash.
+ static void InitFeaturesPostFieldTrial();
+#endif
+
+ // Returns the default thread stack size set by chrome. If we do not
+ // explicitly set default size then returns 0.
+ static size_t GetDefaultThreadStackSize();
+
+#if BUILDFLAG(IS_APPLE)
+ // Stores the period value in TLS.
+ static void SetCurrentThreadRealtimePeriodValue(TimeDelta realtime_period);
+#endif
+
+ static ThreadPriorityForTest GetCurrentThreadPriorityForTest();
};
+namespace internal {
+
+void SetCurrentThreadType(ThreadType thread_type,
+ MessagePumpType pump_type_hint);
+
+void SetCurrentThreadTypeImpl(ThreadType thread_type,
+ MessagePumpType pump_type_hint);
+
+} // namespace internal
+
} // namespace base
#endif // BASE_THREADING_PLATFORM_THREAD_H_
diff --git a/base/threading/platform_thread_android.cc b/base/threading/platform_thread_android.cc
index 8605d96..13453e1 100644
--- a/base/threading/platform_thread_android.cc
+++ b/base/threading/platform_thread_android.cc
@@ -1,60 +1,87 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/platform_thread.h"
#include <errno.h>
+#include <stddef.h>
#include <sys/prctl.h>
-#include <sys/resource.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/android/jni_android.h"
+#include "base/base_jni_headers/ThreadUtils_jni.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/thread_id_name_manager.h"
-#include "jni/ThreadUtils_jni.h"
-#include "starboard/types.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
namespace base {
namespace internal {
-// - BACKGROUND corresponds to Android's PRIORITY_BACKGROUND = 10 value and can
+// - kRealtimeAudio corresponds to Android's PRIORITY_AUDIO = -16 value.
+// - kDisplay corresponds to Android's PRIORITY_DISPLAY = -4 value.
+// - kBackground corresponds to Android's PRIORITY_BACKGROUND = 10 value and can
// result in heavy throttling and force the thread onto a little core on
// big.LITTLE devices.
-// - DISPLAY corresponds to Android's PRIORITY_DISPLAY = -4 value.
-// - REALTIME_AUDIO corresponds to Android's PRIORITY_AUDIO = -16 value.
-const ThreadPriorityToNiceValuePair kThreadPriorityToNiceValueMap[4] = {
- {ThreadPriority::BACKGROUND, 10},
- {ThreadPriority::NORMAL, 0},
- {ThreadPriority::DISPLAY, -4},
- {ThreadPriority::REALTIME_AUDIO, -16},
+const ThreadPriorityToNiceValuePairForTest
+ kThreadPriorityToNiceValueMapForTest[5] = {
+ {ThreadPriorityForTest::kRealtimeAudio, -16},
+ {ThreadPriorityForTest::kDisplay, -4},
+ {ThreadPriorityForTest::kNormal, 0},
+ {ThreadPriorityForTest::kUtility, 1},
+ {ThreadPriorityForTest::kBackground, 10},
};
-bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority) {
+// - kBackground corresponds to Android's PRIORITY_BACKGROUND = 10 value and can
+// result in heavy throttling and force the thread onto a little core on
+// big.LITTLE devices.
+// - kUtility corresponds to Android's THREAD_PRIORITY_LESS_FAVORABLE = 1 value.
+// - kCompositing and kDisplayCritical corresponds to Android's PRIORITY_DISPLAY
+// = -4 value.
+// - kRealtimeAudio corresponds to Android's PRIORITY_AUDIO = -16 value.
+const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7] = {
+ {ThreadType::kBackground, 10}, {ThreadType::kUtility, 1},
+ {ThreadType::kResourceEfficient, 0}, {ThreadType::kDefault, 0},
+ {ThreadType::kCompositing, -4}, {ThreadType::kDisplayCritical, -4},
+ {ThreadType::kRealtimeAudio, -16},
+};
+
+bool CanSetThreadTypeToRealtimeAudio() {
+ return true;
+}
+
+bool SetCurrentThreadTypeForPlatform(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
// On Android, we set the Audio priority through JNI as Audio priority
// will also allow the process to run while it is backgrounded.
- if (priority == ThreadPriority::REALTIME_AUDIO) {
+ if (thread_type == ThreadType::kRealtimeAudio) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_ThreadUtils_setThreadPriorityAudio(env, PlatformThread::CurrentId());
return true;
}
+ // Recent versions of Android (O+) up the priority of the UI thread
+ // automatically.
+ if (thread_type == ThreadType::kCompositing &&
+ pump_type_hint == MessagePumpType::UI &&
+ GetCurrentThreadNiceValue() <=
+ ThreadTypeToNiceValue(ThreadType::kCompositing)) {
+ return true;
+ }
return false;
}
-bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority) {
- DCHECK(priority);
- *priority = ThreadPriority::NORMAL;
+absl::optional<ThreadPriorityForTest>
+GetCurrentThreadPriorityForPlatformForTest() {
JNIEnv* env = base::android::AttachCurrentThread();
if (Java_ThreadUtils_isThreadPriorityAudio(
env, PlatformThread::CurrentId())) {
- *priority = ThreadPriority::REALTIME_AUDIO;
- return true;
+ return absl::make_optional(ThreadPriorityForTest::kRealtimeAudio);
}
- return false;
+ return absl::nullopt;
}
} // namespace internal
diff --git a/base/threading/platform_thread_fuchsia.cc b/base/threading/platform_thread_fuchsia.cc
index be8ac50..fd3ab6dd 100644
--- a/base/threading/platform_thread_fuchsia.cc
+++ b/base/threading/platform_thread_fuchsia.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -8,12 +8,73 @@
#include <sched.h>
#include <zircon/syscalls.h>
+#include <mutex>
+
+#include <fidl/fuchsia.media/cpp/fidl.h>
+#include <lib/fdio/directory.h>
+#include <lib/sys/cpp/component_context.h>
+
+#include "base/fuchsia/fuchsia_component_connect.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/scheduler.h"
+#include "base/no_destructor.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/thread_id_name_manager.h"
-#include "starboard/types.h"
+#include "base/threading/thread_local_storage.h"
namespace base {
+namespace {
+
+fidl::SyncClient<fuchsia_media::ProfileProvider> ConnectProfileProvider() {
+ auto profile_provider_client_end =
+ base::fuchsia_component::Connect<fuchsia_media::ProfileProvider>();
+ if (profile_provider_client_end.is_error()) {
+ LOG(ERROR) << base::FidlConnectionErrorMessage(profile_provider_client_end);
+ return {};
+ }
+ return fidl::SyncClient(std::move(profile_provider_client_end.value()));
+}
+
+// Sets the current thread to the given scheduling role, optionally including
+// hints about the workload period and max CPU runtime (capacity * period) in
+// that period.
+// TODO(crbug.com/1365682): Migrate to the new fuchsia.scheduler.ProfileProvider
+// API when available.
+void SetThreadRole(StringPiece role_name,
+ TimeDelta period = {},
+ float capacity = 0.0f) {
+ DCHECK_GE(capacity, 0.0);
+ DCHECK_LE(capacity, 1.0);
+
+ static const base::NoDestructor<
+ fidl::SyncClient<fuchsia_media::ProfileProvider>>
+ profile_provider(ConnectProfileProvider());
+
+ if (!profile_provider->is_valid()) {
+ return;
+ }
+
+ zx::thread dup_thread;
+ zx_status_t status =
+ zx::thread::self()->duplicate(ZX_RIGHT_SAME_RIGHTS, &dup_thread);
+ ZX_CHECK(status == ZX_OK, status) << "zx_object_duplicate";
+
+ std::string role_selector{role_name};
+ auto result = (*profile_provider)
+ ->RegisterHandlerWithCapacity(
+ {{.thread_handle = std::move(dup_thread),
+ .name = role_selector,
+ .period = period.ToZxDuration(),
+ .capacity = capacity}});
+ if (result.is_error()) {
+ ZX_DLOG(ERROR, result.error_value().status())
+ << "Failed call to RegisterHandlerWithCapacity";
+ }
+}
+
+} // namespace
+
void InitThreading() {}
void TerminateOnThread() {}
@@ -32,20 +93,69 @@
}
// static
-bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
- return false;
+bool PlatformThread::CanChangeThreadType(ThreadType from, ThreadType to) {
+ return from == to || to == ThreadType::kDisplayCritical ||
+ to == ThreadType::kRealtimeAudio;
}
-// static
-void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) {
- if (priority != ThreadPriority::NORMAL) {
- NOTIMPLEMENTED() << "setting ThreadPriority " << static_cast<int>(priority);
+namespace internal {
+
+void SetCurrentThreadTypeImpl(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
+ switch (thread_type) {
+ case ThreadType::kDefault:
+ SetThreadRole("chromium.base.threading.default");
+
+ break;
+
+ case ThreadType::kBackground:
+ SetThreadRole("chromium.base.threading.background");
+ break;
+
+ case ThreadType::kUtility:
+ SetThreadRole("chromium.base.threading.utility");
+ break;
+
+ case ThreadType::kResourceEfficient:
+ SetThreadRole("chromium.base.threading.resource-efficient");
+ break;
+
+ case ThreadType::kCompositing:
+ SetThreadRole("chromium.base.threading.compositing",
+ kDisplaySchedulingPeriod, kDisplaySchedulingCapacity);
+ break;
+
+ case ThreadType::kDisplayCritical:
+ SetThreadRole("chromium.base.threading.display", kDisplaySchedulingPeriod,
+ kDisplaySchedulingCapacity);
+ break;
+
+ case ThreadType::kRealtimeAudio:
+ SetThreadRole("chromium.base.threading.realtime-audio",
+ kAudioSchedulingPeriod, kAudioSchedulingCapacity);
+ break;
}
}
+} // namespace internal
+
// static
-ThreadPriority PlatformThread::GetCurrentThreadPriority() {
- return ThreadPriority::NORMAL;
+ThreadPriorityForTest PlatformThread::GetCurrentThreadPriorityForTest() {
+ // Fuchsia doesn't provide a way to get the current thread's priority.
+ // Use ThreadType stored in TLS as a proxy.
+ const ThreadType thread_type = PlatformThread::GetCurrentThreadType();
+ switch (thread_type) {
+ case ThreadType::kBackground:
+ case ThreadType::kUtility:
+ case ThreadType::kResourceEfficient:
+ case ThreadType::kDefault:
+ case ThreadType::kCompositing:
+ return ThreadPriorityForTest::kNormal;
+ case ThreadType::kDisplayCritical:
+ return ThreadPriorityForTest::kDisplay;
+ case ThreadType::kRealtimeAudio:
+ return ThreadPriorityForTest::kRealtimeAudio;
+ }
}
} // namespace base
diff --git a/base/threading/platform_thread_internal_posix.cc b/base/threading/platform_thread_internal_posix.cc
index 378a24d..b7d0bdf 100644
--- a/base/threading/platform_thread_internal_posix.cc
+++ b/base/threading/platform_thread_internal_posix.cc
@@ -1,37 +1,63 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/platform_thread_internal_posix.h"
+#include <errno.h>
+#include <sys/resource.h>
+
+#include <ostream>
+
#include "base/containers/adapters.h"
#include "base/logging.h"
+#include "base/notreached.h"
namespace base {
namespace internal {
-int ThreadPriorityToNiceValue(ThreadPriority priority) {
- for (const auto& pair : kThreadPriorityToNiceValueMap) {
- if (pair.priority == priority)
+int ThreadTypeToNiceValue(ThreadType thread_type) {
+ for (const auto& pair : kThreadTypeToNiceValueMap) {
+ if (pair.thread_type == thread_type)
return pair.nice_value;
}
- NOTREACHED() << "Unknown ThreadPriority";
+ NOTREACHED() << "Unknown ThreadType";
return 0;
}
-ThreadPriority NiceValueToThreadPriority(int nice_value) {
+ThreadPriorityForTest NiceValueToThreadPriorityForTest(int nice_value) {
// Try to find a priority that best describes |nice_value|. If there isn't
// an exact match, this method returns the closest priority whose nice value
// is higher (lower priority) than |nice_value|.
- for (const auto& pair : Reversed(kThreadPriorityToNiceValueMap)) {
+ for (const auto& pair : kThreadPriorityToNiceValueMapForTest) {
if (pair.nice_value >= nice_value)
return pair.priority;
}
// Reaching here means |nice_value| is more than any of the defined
// priorities. The lowest priority is suitable in this case.
- return ThreadPriority::BACKGROUND;
+ return ThreadPriorityForTest::kBackground;
+}
+
+int GetCurrentThreadNiceValue() {
+#if BUILDFLAG(IS_NACL)
+ NOTIMPLEMENTED();
+ return 0;
+#else
+
+ // Need to clear errno before calling getpriority():
+ // http://man7.org/linux/man-pages/man2/getpriority.2.html
+ errno = 0;
+ int nice_value = getpriority(PRIO_PROCESS, 0);
+ if (errno != 0) {
+ DVPLOG(1) << "Failed to get nice value of thread ("
+ << PlatformThread::CurrentId() << ")";
+ return 0;
+ }
+
+ return nice_value;
+#endif
}
} // namespace internal
diff --git a/base/threading/platform_thread_internal_posix.h b/base/threading/platform_thread_internal_posix.h
index 5f4a215..120abae 100644
--- a/base/threading/platform_thread_internal_posix.h
+++ b/base/threading/platform_thread_internal_posix.h
@@ -1,4 +1,4 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
+// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -7,39 +7,66 @@
#include "base/base_export.h"
#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
namespace base {
namespace internal {
-struct ThreadPriorityToNiceValuePair {
- ThreadPriority priority;
+struct ThreadTypeToNiceValuePair {
+ ThreadType thread_type;
int nice_value;
};
+
+struct ThreadPriorityToNiceValuePairForTest {
+ ThreadPriorityForTest priority;
+ int nice_value;
+};
+
// The elements must be listed in the order of increasing priority (lowest
// priority first), that is, in the order of decreasing nice values (highest
// nice value first).
-BASE_EXPORT extern
-const ThreadPriorityToNiceValuePair kThreadPriorityToNiceValueMap[4];
+extern const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7];
+
+// The elements must be listed in the order of decreasing priority (highest
+// priority first), that is, in the order of increasing nice values (lowest nice
+// value first).
+extern const ThreadPriorityToNiceValuePairForTest
+ kThreadPriorityToNiceValueMapForTest[5];
// Returns the nice value matching |priority| based on the platform-specific
-// implementation of kThreadPriorityToNiceValueMap.
-int ThreadPriorityToNiceValue(ThreadPriority priority);
+// implementation of kThreadTypeToNiceValueMap.
+int ThreadTypeToNiceValue(ThreadType thread_type);
-// Returns the ThreadPrioirty matching |nice_value| based on the platform-
-// specific implementation of kThreadPriorityToNiceValueMap.
-BASE_EXPORT ThreadPriority NiceValueToThreadPriority(int nice_value);
+// Returns whether SetCurrentThreadTypeForPlatform can set a thread as
+// kRealtimeAudio.
+bool CanSetThreadTypeToRealtimeAudio();
// Allows platform specific tweaks to the generic POSIX solution for
-// SetCurrentThreadPriority. Returns true if the platform-specific
-// implementation handled this |priority| change, false if the generic
+// SetCurrentThreadType(). Returns true if the platform-specific
+// implementation handled this |thread_type| change, false if the generic
// implementation should instead proceed.
-bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority);
+bool SetCurrentThreadTypeForPlatform(ThreadType thread_type,
+ MessagePumpType pump_type_hint);
-// Returns true if there is a platform-specific ThreadPriority set on the
-// current thread (and returns the actual ThreadPriority via |priority|).
-// Returns false otherwise, leaving |priority| untouched.
-bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority);
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+// Current thread id is cached in thread local storage for performance reasons.
+// In some rare cases it's important to invalidate that cache explicitly (e.g.
+// after going through clone() syscall which does not call pthread_atfork()
+// handlers).
+// This can only be called when the process is single-threaded.
+BASE_EXPORT void InvalidateTidCache();
+#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
+// Returns the ThreadPrioirtyForTest matching |nice_value| based on the
+// platform-specific implementation of kThreadPriorityToNiceValueMapForTest.
+ThreadPriorityForTest NiceValueToThreadPriorityForTest(int nice_value);
+
+absl::optional<ThreadPriorityForTest>
+GetCurrentThreadPriorityForPlatformForTest();
+
+int GetCurrentThreadNiceValue();
} // namespace internal
diff --git a/base/threading/platform_thread_linux.cc b/base/threading/platform_thread_linux.cc
index d1c6a5c..b365237 100644
--- a/base/threading/platform_thread_linux.cc
+++ b/base/threading/platform_thread_linux.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,41 +6,163 @@
#include <errno.h>
#include <sched.h>
+#include <stddef.h>
+#include <cstdint>
+#include <atomic>
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/notreached.h"
+#include "base/process/internal_linux.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/thread_id_name_manager.h"
+#include "base/threading/thread_type_delegate.h"
#include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
-#if !defined(OS_NACL) && !defined(OS_AIX)
+#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
#include <pthread.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
-
-#include "starboard/types.h"
#endif
namespace base {
+
+#if BUILDFLAG(IS_CHROMEOS)
+BASE_FEATURE(kSchedUtilHints,
+ "SchedUtilHints",
+ base::FEATURE_ENABLED_BY_DEFAULT);
+#endif
+
namespace {
-#if !defined(OS_NACL)
+
+#if !BUILDFLAG(IS_NACL)
+ThreadTypeDelegate* g_thread_type_delegate = nullptr;
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS)
+std::atomic<bool> g_use_sched_util(true);
+std::atomic<bool> g_scheduler_hints_adjusted(false);
+
+// When a device doesn't specify uclamp values via chrome switches,
+// default boosting for urgent tasks is hardcoded here as 20%.
+// Higher values can lead to higher power consumption thus this value
+// is chosen conservatively where it does not show noticeable
+// power usage increased from several perf/power tests.
+const int kSchedulerBoostDef = 20;
+const int kSchedulerLimitDef = 100;
+const bool kSchedulerUseLatencyTuneDef = true;
+
+int g_scheduler_boost_adj;
+int g_scheduler_limit_adj;
+bool g_scheduler_use_latency_tune_adj;
+
+#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
+
+// Defined by linux uclamp ABI of sched_setattr().
+const uint32_t kSchedulerUclampMin = 0;
+const uint32_t kSchedulerUclampMax = 1024;
+
+// sched_attr is used to set scheduler attributes for Linux. It is not a POSIX
+// struct and glibc does not expose it.
+struct sched_attr {
+ uint32_t size;
+
+ uint32_t sched_policy;
+ uint64_t sched_flags;
+
+ /* SCHED_NORMAL, SCHED_BATCH */
+ __s32 sched_nice;
+
+ /* SCHED_FIFO, SCHED_RR */
+ uint32_t sched_priority;
+
+ /* SCHED_DEADLINE */
+ uint64_t sched_runtime;
+ uint64_t sched_deadline;
+ uint64_t sched_period;
+
+ /* Utilization hints */
+ uint32_t sched_util_min;
+ uint32_t sched_util_max;
+};
+
+#if !defined(__NR_sched_setattr)
+#if defined(__x86_64__)
+#define __NR_sched_setattr 314
+#define __NR_sched_getattr 315
+#elif defined(__i386__)
+#define __NR_sched_setattr 351
+#define __NR_sched_getattr 352
+#elif defined(__arm__)
+#define __NR_sched_setattr 380
+#define __NR_sched_getattr 381
+#elif defined(__aarch64__)
+#define __NR_sched_setattr 274
+#define __NR_sched_getattr 275
+#else
+#error "We don't have an __NR_sched_setattr for this architecture."
+#endif
+#endif
+
+#if !defined(SCHED_FLAG_UTIL_CLAMP_MIN)
+#define SCHED_FLAG_UTIL_CLAMP_MIN 0x20
+#endif
+
+#if !defined(SCHED_FLAG_UTIL_CLAMP_MAX)
+#define SCHED_FLAG_UTIL_CLAMP_MAX 0x40
+#endif
+
+long sched_getattr(pid_t pid,
+ const struct sched_attr* attr,
+ unsigned int size,
+ unsigned int flags) {
+ return syscall(__NR_sched_getattr, pid, attr, size, flags);
+}
+
+long sched_setattr(pid_t pid,
+ const struct sched_attr* attr,
+ unsigned int flags) {
+ return syscall(__NR_sched_setattr, pid, attr, flags);
+}
+#endif // !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
+#endif // BUILDFLAG(IS_CHROMEOS)
+
+#if !BUILDFLAG(IS_NACL)
const FilePath::CharType kCgroupDirectory[] =
FILE_PATH_LITERAL("/sys/fs/cgroup");
-FilePath ThreadPriorityToCgroupDirectory(const FilePath& cgroup_filepath,
- ThreadPriority priority) {
- switch (priority) {
- case ThreadPriority::NORMAL:
- return cgroup_filepath;
- case ThreadPriority::BACKGROUND:
+FilePath ThreadTypeToCgroupDirectory(const FilePath& cgroup_filepath,
+ ThreadType thread_type) {
+ switch (thread_type) {
+ case ThreadType::kBackground:
+ case ThreadType::kUtility:
+ case ThreadType::kResourceEfficient:
return cgroup_filepath.Append(FILE_PATH_LITERAL("non-urgent"));
- case ThreadPriority::DISPLAY:
- case ThreadPriority::REALTIME_AUDIO:
+ case ThreadType::kDefault:
+ return cgroup_filepath;
+ case ThreadType::kCompositing:
+#if BUILDFLAG(IS_CHROMEOS)
+ // On ChromeOS, kCompositing is also considered urgent.
+ return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
+#else
+ // TODO(1329208): Experiment with bringing IS_LINUX inline with
+ // IS_CHROMEOS.
+ return cgroup_filepath;
+#endif
+ case ThreadType::kDisplayCritical:
+ case ThreadType::kRealtimeAudio:
return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
}
NOTREACHED();
@@ -50,19 +172,21 @@
void SetThreadCgroup(PlatformThreadId thread_id,
const FilePath& cgroup_directory) {
FilePath tasks_filepath = cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
- std::string tid = IntToString(thread_id);
- int bytes_written = WriteFile(tasks_filepath, tid.c_str(), tid.size());
- if (bytes_written != static_cast<int>(tid.size())) {
+ std::string tid = NumberToString(thread_id);
+ // TODO(crbug.com/1333521): Remove cast.
+ const int size = static_cast<int>(tid.size());
+ int bytes_written = WriteFile(tasks_filepath, tid.data(), size);
+ if (bytes_written != size) {
DVLOG(1) << "Failed to add " << tid << " to " << tasks_filepath.value();
}
}
-void SetThreadCgroupForThreadPriority(PlatformThreadId thread_id,
- const FilePath& cgroup_filepath,
- ThreadPriority priority) {
+void SetThreadCgroupForThreadType(PlatformThreadId thread_id,
+ const FilePath& cgroup_filepath,
+ ThreadType thread_type) {
// Append "chrome" suffix.
- FilePath cgroup_directory = ThreadPriorityToCgroupDirectory(
- cgroup_filepath.Append(FILE_PATH_LITERAL("chrome")), priority);
+ FilePath cgroup_directory = ThreadTypeToCgroupDirectory(
+ cgroup_filepath.Append(FILE_PATH_LITERAL("chrome")), thread_type);
// Silently ignore request if cgroup directory doesn't exist.
if (!DirectoryExists(cgroup_directory))
@@ -71,14 +195,117 @@
SetThreadCgroup(thread_id, cgroup_directory);
}
-void SetThreadCgroupsForThreadPriority(PlatformThreadId thread_id,
- ThreadPriority priority) {
+#if BUILDFLAG(IS_CHROMEOS)
+// thread_id should always be the value in the root PID namespace (see
+// FindThreadID).
+void SetThreadLatencySensitivity(ProcessId process_id,
+ PlatformThreadId thread_id,
+ ThreadType thread_type) {
+ struct sched_attr attr;
+ bool is_urgent = false;
+ int boost_percent, limit_percent;
+ int latency_sensitive_urgent;
+
+ // Scheduler boost defaults to true unless disabled.
+ if (!g_use_sched_util.load())
+ return;
+
+ // FieldTrial API can be called only once features were parsed.
+ if (g_scheduler_hints_adjusted.load()) {
+ boost_percent = g_scheduler_boost_adj;
+ limit_percent = g_scheduler_limit_adj;
+ latency_sensitive_urgent = g_scheduler_use_latency_tune_adj;
+ } else {
+ boost_percent = kSchedulerBoostDef;
+ limit_percent = kSchedulerLimitDef;
+ latency_sensitive_urgent = kSchedulerUseLatencyTuneDef;
+ }
+
+ // The thread_id passed in here is either 0 (in which case we ste for current
+ // thread), or is a tid that is not the NS tid but the global one. The
+ // conversion from NS tid to global tid is done by the callers using
+ // FindThreadID().
+ std::string thread_dir;
+ if (thread_id)
+ thread_dir = base::StringPrintf("/proc/%d/task/%d/", process_id, thread_id);
+ else
+ thread_dir = "/proc/thread-self/";
+
+ // Silently ignore request if thread directory doesn't exist.
+ if (!DirectoryExists(FilePath(thread_dir)))
+ return;
+
+ FilePath latency_sensitive_file = FilePath(thread_dir + "latency_sensitive");
+
+ if (!PathExists(latency_sensitive_file))
+ return;
+
+ // Silently ignore if getattr fails due to sandboxing.
+ if (sched_getattr(thread_id, &attr, sizeof(attr), 0) == -1 ||
+ attr.size != sizeof(attr))
+ return;
+
+ switch (thread_type) {
+ case ThreadType::kBackground:
+ case ThreadType::kUtility:
+ case ThreadType::kResourceEfficient:
+ case ThreadType::kDefault:
+ break;
+ case ThreadType::kCompositing:
+ case ThreadType::kDisplayCritical:
+ // Compositing and display critical threads need a boost for consistent 60
+ // fps.
+ [[fallthrough]];
+ case ThreadType::kRealtimeAudio:
+ is_urgent = true;
+ break;
+ }
+
+ if (is_urgent && latency_sensitive_urgent) {
+ PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "1", 1))
+ << "Failed to write latency file.\n";
+ } else {
+ PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "0", 1))
+ << "Failed to write latency file.\n";
+ }
+
+ attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MIN;
+ attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MAX;
+
+ if (is_urgent) {
+ attr.sched_util_min =
+ (saturated_cast<uint32_t>(boost_percent) * kSchedulerUclampMax + 50) /
+ 100;
+ attr.sched_util_max = kSchedulerUclampMax;
+ } else {
+ attr.sched_util_min = kSchedulerUclampMin;
+ attr.sched_util_max =
+ (saturated_cast<uint32_t>(limit_percent) * kSchedulerUclampMax + 50) /
+ 100;
+ }
+
+ DCHECK_GE(attr.sched_util_min, kSchedulerUclampMin);
+ DCHECK_LE(attr.sched_util_max, kSchedulerUclampMax);
+
+ attr.size = sizeof(struct sched_attr);
+ if (sched_setattr(thread_id, &attr, 0) == -1) {
+ // We log it as an error because, if the PathExists above succeeded, we
+ // expect this syscall to also work since the kernel is new'ish.
+ PLOG_IF(ERROR, errno != E2BIG)
+ << "Failed to set sched_util_min, performance may be effected.\n";
+ }
+}
+#endif
+
+void SetThreadCgroupsForThreadType(PlatformThreadId thread_id,
+ ThreadType thread_type) {
FilePath cgroup_filepath(kCgroupDirectory);
- SetThreadCgroupForThreadPriority(
- thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset")), priority);
- SetThreadCgroupForThreadPriority(
+ SetThreadCgroupForThreadType(
+ thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset")),
+ thread_type);
+ SetThreadCgroupForThreadType(
thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("schedtune")),
- priority);
+ thread_type);
}
#endif
} // namespace
@@ -86,41 +313,82 @@
namespace internal {
namespace {
-#if !defined(OS_NACL)
+#if !BUILDFLAG(IS_NACL)
const struct sched_param kRealTimePrio = {8};
#endif
} // namespace
-const ThreadPriorityToNiceValuePair kThreadPriorityToNiceValueMap[4] = {
- {ThreadPriority::BACKGROUND, 10},
- {ThreadPriority::NORMAL, 0},
- {ThreadPriority::DISPLAY, -8},
- {ThreadPriority::REALTIME_AUDIO, -10},
+const ThreadPriorityToNiceValuePairForTest
+ kThreadPriorityToNiceValueMapForTest[5] = {
+ {ThreadPriorityForTest::kRealtimeAudio, -10},
+ {ThreadPriorityForTest::kDisplay, -8},
+ {ThreadPriorityForTest::kNormal, 0},
+ {ThreadPriorityForTest::kUtility, 1},
+ {ThreadPriorityForTest::kBackground, 10},
};
-bool SetCurrentThreadPriorityForPlatform(ThreadPriority priority) {
-#if !defined(OS_NACL)
- SetThreadCgroupsForThreadPriority(PlatformThread::CurrentId(), priority);
- return priority == ThreadPriority::REALTIME_AUDIO &&
+const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7] = {
+ {ThreadType::kBackground, 10}, {ThreadType::kUtility, 1},
+ {ThreadType::kResourceEfficient, 0}, {ThreadType::kDefault, 0},
+#if BUILDFLAG(IS_CHROMEOS)
+ {ThreadType::kCompositing, -8},
+#else
+ // TODO(1329208): Experiment with bringing IS_LINUX inline with IS_CHROMEOS.
+ {ThreadType::kCompositing, 0},
+#endif
+ {ThreadType::kDisplayCritical, -8}, {ThreadType::kRealtimeAudio, -10},
+};
+
+bool CanSetThreadTypeToRealtimeAudio() {
+#if !BUILDFLAG(IS_NACL)
+ // A non-zero soft-limit on RLIMIT_RTPRIO is required to be allowed to invoke
+ // pthread_setschedparam in SetCurrentThreadTypeForPlatform().
+ struct rlimit rlim;
+ return getrlimit(RLIMIT_RTPRIO, &rlim) != 0 && rlim.rlim_cur != 0;
+#else
+ return false;
+#endif
+}
+
+bool SetCurrentThreadTypeForPlatform(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
+#if !BUILDFLAG(IS_NACL)
+ const PlatformThreadId tid = PlatformThread::CurrentId();
+
+ if (g_thread_type_delegate &&
+ g_thread_type_delegate->HandleThreadTypeChange(tid, thread_type)) {
+ return true;
+ }
+
+ // For legacy schedtune interface
+ SetThreadCgroupsForThreadType(tid, thread_type);
+
+#if BUILDFLAG(IS_CHROMEOS)
+ // For upstream uclamp interface. We try both legacy (schedtune, as done
+ // earlier) and upstream (uclamp) interfaces, and whichever succeeds wins.
+ SetThreadLatencySensitivity(0 /* ignore */, 0 /* thread-self */, thread_type);
+#endif
+
+ return thread_type == ThreadType::kRealtimeAudio &&
pthread_setschedparam(pthread_self(), SCHED_RR, &kRealTimePrio) == 0;
#else
return false;
#endif
}
-bool GetCurrentThreadPriorityForPlatform(ThreadPriority* priority) {
-#if !defined(OS_NACL)
+absl::optional<ThreadPriorityForTest>
+GetCurrentThreadPriorityForPlatformForTest() {
+#if !BUILDFLAG(IS_NACL)
int maybe_sched_rr = 0;
struct sched_param maybe_realtime_prio = {0};
if (pthread_getschedparam(pthread_self(), &maybe_sched_rr,
&maybe_realtime_prio) == 0 &&
maybe_sched_rr == SCHED_RR &&
maybe_realtime_prio.sched_priority == kRealTimePrio.sched_priority) {
- *priority = ThreadPriority::REALTIME_AUDIO;
- return true;
+ return absl::make_optional(ThreadPriorityForTest::kRealtimeAudio);
}
#endif
- return false;
+ return absl::nullopt;
}
} // namespace internal
@@ -129,7 +397,7 @@
void PlatformThread::SetName(const std::string& name) {
ThreadIdNameManager::GetInstance()->SetName(name);
-#if !defined(OS_NACL) && !defined(OS_AIX)
+#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
// On linux we can get the thread names to show up in the debugger by setting
// the process name for the LWP. We don't want to do this for the main
// thread because that would rename the process, causing tools like killall
@@ -146,35 +414,91 @@
// We expect EPERM failures in sandboxed processes, just ignore those.
if (err < 0 && errno != EPERM)
DPLOG(ERROR) << "prctl(PR_SET_NAME)";
-#endif // !defined(OS_NACL) && !defined(OS_AIX)
+#endif // !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
}
-#if !defined(OS_NACL) && !defined(OS_AIX)
+#if !BUILDFLAG(IS_NACL)
// static
-void PlatformThread::SetThreadPriority(PlatformThreadId thread_id,
- ThreadPriority priority) {
- // Changing current main threads' priority is not permitted in favor of
- // security, this interface is restricted to change only non-main thread
- // priority.
- CHECK_NE(thread_id, getpid());
+void PlatformThread::SetThreadTypeDelegate(ThreadTypeDelegate* delegate) {
+ // A component cannot override a delegate set by another component, thus
+ // disallow setting a delegate when one already exists.
+ DCHECK(!g_thread_type_delegate || !delegate);
- SetThreadCgroupsForThreadPriority(thread_id, priority);
+ g_thread_type_delegate = delegate;
+}
+#endif
- const int nice_setting = internal::ThreadPriorityToNiceValue(priority);
- if (setpriority(PRIO_PROCESS, thread_id, nice_setting)) {
+#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
+// static
+void PlatformThread::SetThreadType(ProcessId process_id,
+ PlatformThreadId thread_id,
+ ThreadType thread_type) {
+ // For legacy schedtune interface
+ SetThreadCgroupsForThreadType(thread_id, thread_type);
+
+#if BUILDFLAG(IS_CHROMEOS)
+ // For upstream uclamp interface. We try both legacy (schedtune, as done
+ // earlier) and upstream (uclamp) interfaces, and whichever succeeds wins.
+ SetThreadLatencySensitivity(process_id, thread_id, thread_type);
+#endif
+
+ const int nice_setting = internal::ThreadTypeToNiceValue(thread_type);
+ if (setpriority(PRIO_PROCESS, static_cast<id_t>(thread_id), nice_setting)) {
DVPLOG(1) << "Failed to set nice value of thread (" << thread_id << ") to "
<< nice_setting;
}
}
-#endif // !defined(OS_NACL) && !defined(OS_AIX)
+#endif // !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
+
+#if BUILDFLAG(IS_CHROMEOS)
+void PlatformThread::InitFeaturesPostFieldTrial() {
+ DCHECK(FeatureList::GetInstance());
+ if (!FeatureList::IsEnabled(kSchedUtilHints)) {
+ g_use_sched_util.store(false);
+ return;
+ }
+
+ int boost_def = kSchedulerBoostDef;
+
+ if (CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSchedulerBoostUrgent)) {
+ std::string boost_switch_str =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kSchedulerBoostUrgent);
+
+ int boost_switch_val;
+ if (!StringToInt(boost_switch_str, &boost_switch_val) ||
+ boost_switch_val < 0 || boost_switch_val > 100) {
+ DVPLOG(1) << "Invalid input for " << switches::kSchedulerBoostUrgent;
+ } else {
+ boost_def = boost_switch_val;
+ }
+ }
+
+ g_scheduler_boost_adj = GetFieldTrialParamByFeatureAsInt(
+ kSchedUtilHints, "BoostUrgent", boost_def);
+ g_scheduler_limit_adj = GetFieldTrialParamByFeatureAsInt(
+ kSchedUtilHints, "LimitNonUrgent", kSchedulerLimitDef);
+ g_scheduler_use_latency_tune_adj = GetFieldTrialParamByFeatureAsBool(
+ kSchedUtilHints, "LatencyTune", kSchedulerUseLatencyTuneDef);
+
+ g_scheduler_hints_adjusted.store(true);
+}
+#endif
void InitThreading() {}
void TerminateOnThread() {}
size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) {
-#if !defined(THREAD_SANITIZER)
+#if !defined(THREAD_SANITIZER) && defined(__GLIBC__)
+ // Generally glibc sets ample default stack sizes, so use the default there.
return 0;
+#elif !defined(THREAD_SANITIZER)
+ // Other libcs (uclibc, musl, etc) tend to use smaller stacks, often too small
+ // for chromium. Make sure we have enough space to work with here. Note that
+ // for comparison glibc stacks are generally around 8MB.
+ return 2 * (1 << 20);
#else
// ThreadSanitizer bloats the stack heavily. Evidence has been that the
// default stack size isn't enough for some browser tests.
diff --git a/base/threading/platform_thread_mac.mm b/base/threading/platform_thread_mac.mm
index c470ce0..fa192b7 100644
--- a/base/threading/platform_thread_mac.mm
+++ b/base/threading/platform_thread_mac.mm
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -8,22 +8,30 @@
#include <mach/mach.h>
#include <mach/mach_time.h>
#include <mach/thread_policy.h>
+#include <mach/thread_switch.h>
#include <stddef.h>
#include <sys/resource.h>
#include <algorithm>
+#include <atomic>
+#include "base/feature_list.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
+#include "base/mac/mac_util.h"
#include "base/mac/mach_logging.h"
+#include "base/metrics/histogram_functions.h"
#include "base/threading/thread_id_name_manager.h"
+#include "base/threading/threading_features.h"
+#include "build/blink_buildflags.h"
#include "build/build_config.h"
namespace base {
namespace {
-NSString* const kThreadPriorityKey = @"CrThreadPriorityKey";
+NSString* const kThreadPriorityForTestKey = @"CrThreadPriorityForTestKey";
+NSString* const kRealtimePeriodNsKey = @"CrRealtimePeriodNsKey";
} // namespace
// If Cocoa is to be used on more than one thread, it must know that the
@@ -47,6 +55,21 @@
}
}
+TimeDelta PlatformThread::Delegate::GetRealtimePeriod() {
+ return TimeDelta();
+}
+
+// static
+void PlatformThread::YieldCurrentThread() {
+ // Don't use sched_yield(), as it can lead to 10ms delays.
+ //
+ // This only depresses the thread priority for 1ms, which is more in line
+ // with what calling code likely wants. See this bug in webkit for context:
+ // https://bugs.webkit.org/show_bug.cgi?id=204871
+ mach_msg_timeout_t timeout_ms = 1;
+ thread_switch(MACH_PORT_NULL, SWITCH_OPTION_DEPRESS, timeout_ms);
+}
+
// static
void PlatformThread::SetName(const std::string& name) {
ThreadIdNameManager::GetInstance()->SetName(name);
@@ -60,47 +83,126 @@
pthread_setname_np(shortened_name.c_str());
}
+// Whether optimized realt-time thread config should be used for audio.
+BASE_FEATURE(kOptimizedRealtimeThreadingMac,
+ "OptimizedRealtimeThreadingMac",
+#if BUILDFLAG(IS_MAC)
+ FEATURE_ENABLED_BY_DEFAULT
+#else
+ FEATURE_DISABLED_BY_DEFAULT
+#endif
+);
+
namespace {
-// Enables time-contraint policy and priority suitable for low-latency,
-// glitch-resistant audio.
-void SetPriorityRealtimeAudio() {
- // Increase thread priority to real-time.
+bool IsOptimizedRealtimeThreadingMacEnabled() {
+#if BUILDFLAG(IS_MAC)
+ // There is some platform bug on 10.14.
+ if (mac::IsOS10_14())
+ return false;
+#endif
- // Please note that the thread_policy_set() calls may fail in
- // rare cases if the kernel decides the system is under heavy load
- // and is unable to handle boosting the thread priority.
- // In these cases we just return early and go on with life.
+ return FeatureList::IsEnabled(kOptimizedRealtimeThreadingMac);
+}
- mach_port_t mach_thread_id =
- pthread_mach_thread_np(PlatformThread::CurrentHandle().platform_handle());
+} // namespace
- // Make thread fixed priority.
- thread_extended_policy_data_t policy;
- policy.timeshare = 0; // Set to 1 for a non-fixed thread.
- kern_return_t result =
- thread_policy_set(mach_thread_id,
- THREAD_EXTENDED_POLICY,
- reinterpret_cast<thread_policy_t>(&policy),
- THREAD_EXTENDED_POLICY_COUNT);
- if (result != KERN_SUCCESS) {
- MACH_DVLOG(1, result) << "thread_policy_set";
- return;
+// Fine-tuning optimized realt-time thread config:
+// Whether or not the thread should be preeptible.
+const FeatureParam<bool> kOptimizedRealtimeThreadingMacPreemptible{
+ &kOptimizedRealtimeThreadingMac, "preemptible", true};
+// Portion of the time quantum the thread is expected to be busy, (0, 1].
+const FeatureParam<double> kOptimizedRealtimeThreadingMacBusy{
+ &kOptimizedRealtimeThreadingMac, "busy", 0.5};
+// Maximum portion of the time quantum the thread is expected to be busy,
+// (kOptimizedRealtimeThreadingMacBusy, 1].
+const FeatureParam<double> kOptimizedRealtimeThreadingMacBusyLimit{
+ &kOptimizedRealtimeThreadingMac, "busy_limit", 1.0};
+
+namespace {
+
+struct TimeConstraints {
+ bool preemptible{kOptimizedRealtimeThreadingMacPreemptible.default_value};
+ double busy{kOptimizedRealtimeThreadingMacBusy.default_value};
+ double busy_limit{kOptimizedRealtimeThreadingMacBusyLimit.default_value};
+
+ static TimeConstraints ReadFromFeatureParams() {
+ double busy_limit = kOptimizedRealtimeThreadingMacBusyLimit.Get();
+ return TimeConstraints{
+ kOptimizedRealtimeThreadingMacPreemptible.Get(),
+ std::min(busy_limit, kOptimizedRealtimeThreadingMacBusy.Get()),
+ busy_limit};
+ }
+};
+
+// Use atomics to access FeatureList values when setting up a thread, since
+// there are cases when FeatureList initialization is not synchronized with
+// PlatformThread creation.
+std::atomic<bool> g_use_optimized_realtime_threading(
+ kOptimizedRealtimeThreadingMac.default_state == FEATURE_ENABLED_BY_DEFAULT);
+std::atomic<TimeConstraints> g_time_constraints;
+
+} // namespace
+
+// static
+void PlatformThread::InitFeaturesPostFieldTrial() {
+ // A DCHECK is triggered on FeatureList initialization if the state of a
+ // feature has been checked before. To avoid triggering this DCHECK in unit
+ // tests that call this before initializing the FeatureList, only check the
+ // state of the feature if the FeatureList is initialized.
+ if (FeatureList::GetInstance()) {
+ g_time_constraints.store(TimeConstraints::ReadFromFeatureParams());
+ g_use_optimized_realtime_threading.store(
+ IsOptimizedRealtimeThreadingMacEnabled());
+ }
+}
+
+// static
+void PlatformThread::SetCurrentThreadRealtimePeriodValue(
+ TimeDelta realtime_period) {
+ if (g_use_optimized_realtime_threading.load()) {
+ [[NSThread currentThread] threadDictionary][kRealtimePeriodNsKey] =
+ @(realtime_period.InNanoseconds());
+ }
+}
+
+namespace {
+
+TimeDelta GetCurrentThreadRealtimePeriod() {
+ NSNumber* period = mac::ObjCCast<NSNumber>(
+ [[NSThread currentThread] threadDictionary][kRealtimePeriodNsKey]);
+
+ return period ? Nanoseconds(period.longLongValue) : TimeDelta();
+}
+
+// Calculates time constrints for THREAD_TIME_CONSTRAINT_POLICY.
+// |realtime_period| is used as a base if it's non-zero.
+// Otherwise we fall back to empirical values.
+thread_time_constraint_policy_data_t GetTimeConstraints(
+ TimeDelta realtime_period) {
+ thread_time_constraint_policy_data_t time_constraints;
+ mach_timebase_info_data_t tb_info;
+ mach_timebase_info(&tb_info);
+
+ if (!realtime_period.is_zero()) {
+ // Limit the lowest value to 2.9 ms we used to have historically. The lower
+ // the period, the more CPU frequency may go up, and we don't want to risk
+ // worsening the thermal situation.
+ uint32_t abs_realtime_period = saturated_cast<uint32_t>(
+ std::max(realtime_period.InNanoseconds(), 2900000LL) *
+ (double(tb_info.denom) / tb_info.numer));
+ TimeConstraints config = g_time_constraints.load();
+ time_constraints.period = abs_realtime_period;
+ time_constraints.constraint = std::min(
+ abs_realtime_period, uint32_t(abs_realtime_period * config.busy_limit));
+ time_constraints.computation =
+ std::min(time_constraints.constraint,
+ uint32_t(abs_realtime_period * config.busy));
+ time_constraints.preemptible = config.preemptible ? YES : NO;
+ return time_constraints;
}
- // Set to relatively high priority.
- thread_precedence_policy_data_t precedence;
- precedence.importance = 63;
- result = thread_policy_set(mach_thread_id,
- THREAD_PRECEDENCE_POLICY,
- reinterpret_cast<thread_policy_t>(&precedence),
- THREAD_PRECEDENCE_POLICY_COUNT);
- if (result != KERN_SUCCESS) {
- MACH_DVLOG(1, result) << "thread_policy_set";
- return;
- }
-
- // Most important, set real-time constraints.
+ // Empirical configuration.
// Define the guaranteed and max fraction of time for the audio thread.
// These "duty cycle" values can range from 0 to 1. A value of 0.5
@@ -124,84 +226,150 @@
// Get the conversion factor from milliseconds to absolute time
// which is what the time-constraints call needs.
- mach_timebase_info_data_t tb_info;
- mach_timebase_info(&tb_info);
- double ms_to_abs_time =
- (static_cast<double>(tb_info.denom) / tb_info.numer) * 1000000;
+ double ms_to_abs_time = double(tb_info.denom) / tb_info.numer * 1000000;
- thread_time_constraint_policy_data_t time_constraints;
time_constraints.period = kTimeQuantum * ms_to_abs_time;
time_constraints.computation = kAudioTimeNeeded * ms_to_abs_time;
time_constraints.constraint = kMaxTimeAllowed * ms_to_abs_time;
time_constraints.preemptible = 0;
+ return time_constraints;
+}
+
+// Enables time-contraint policy and priority suitable for low-latency,
+// glitch-resistant audio.
+void SetPriorityRealtimeAudio(TimeDelta realtime_period) {
+ // Increase thread priority to real-time.
+
+ // Please note that the thread_policy_set() calls may fail in
+ // rare cases if the kernel decides the system is under heavy load
+ // and is unable to handle boosting the thread priority.
+ // In these cases we just return early and go on with life.
+
+ mach_port_t mach_thread_id =
+ pthread_mach_thread_np(PlatformThread::CurrentHandle().platform_handle());
+
+ // Make thread fixed priority.
+ thread_extended_policy_data_t policy;
+ policy.timeshare = 0; // Set to 1 for a non-fixed thread.
+ kern_return_t result = thread_policy_set(
+ mach_thread_id, THREAD_EXTENDED_POLICY,
+ reinterpret_cast<thread_policy_t>(&policy), THREAD_EXTENDED_POLICY_COUNT);
+ if (result != KERN_SUCCESS) {
+ MACH_DVLOG(1, result) << "thread_policy_set";
+ return;
+ }
+
+ // Set to relatively high priority.
+ thread_precedence_policy_data_t precedence;
+ precedence.importance = 63;
+ result = thread_policy_set(mach_thread_id, THREAD_PRECEDENCE_POLICY,
+ reinterpret_cast<thread_policy_t>(&precedence),
+ THREAD_PRECEDENCE_POLICY_COUNT);
+ if (result != KERN_SUCCESS) {
+ MACH_DVLOG(1, result) << "thread_policy_set";
+ return;
+ }
+
+ // Most important, set real-time constraints.
+
+ thread_time_constraint_policy_data_t time_constraints =
+ GetTimeConstraints(realtime_period);
result =
- thread_policy_set(mach_thread_id,
- THREAD_TIME_CONSTRAINT_POLICY,
+ thread_policy_set(mach_thread_id, THREAD_TIME_CONSTRAINT_POLICY,
reinterpret_cast<thread_policy_t>(&time_constraints),
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
MACH_DVLOG_IF(1, result != KERN_SUCCESS, result) << "thread_policy_set";
-
return;
}
} // anonymous namespace
// static
-bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
+bool PlatformThread::CanChangeThreadType(ThreadType from, ThreadType to) {
return true;
}
-// static
-void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) {
- // Changing the priority of the main thread causes performance regressions.
- // https://crbug.com/601270
- DCHECK(![[NSThread currentThread] isMainThread]);
+namespace internal {
- switch (priority) {
- case ThreadPriority::BACKGROUND:
- [[NSThread currentThread] setThreadPriority:0];
+void SetCurrentThreadTypeImpl(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
+ // Changing the priority of the main thread causes performance
+ // regressions. https://crbug.com/601270
+ // TODO(1280764): Remove this check. kCompositing is the default on Mac, so
+ // this check is counter intuitive.
+ if ([[NSThread currentThread] isMainThread] &&
+ thread_type >= ThreadType::kCompositing) {
+ DCHECK(thread_type == ThreadType::kDefault ||
+ thread_type == ThreadType::kCompositing);
+ return;
+ }
+
+ ThreadPriorityForTest priority = ThreadPriorityForTest::kNormal;
+ switch (thread_type) {
+ case ThreadType::kBackground:
+ priority = ThreadPriorityForTest::kBackground;
+ pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0);
break;
- case ThreadPriority::NORMAL:
- case ThreadPriority::DISPLAY:
- [[NSThread currentThread] setThreadPriority:0.5];
+ case ThreadType::kUtility:
+ priority = ThreadPriorityForTest::kUtility;
+ pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
break;
- case ThreadPriority::REALTIME_AUDIO:
- SetPriorityRealtimeAudio();
+ case ThreadType::kResourceEfficient:
+ priority = ThreadPriorityForTest::kUtility;
+ pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
+ break;
+ case ThreadType::kDefault:
+ // TODO(1329208): Experiment with prioritizing kCompositing on Mac like on
+ // other platforms.
+ [[fallthrough]];
+ case ThreadType::kCompositing:
+ priority = ThreadPriorityForTest::kNormal;
+ pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0);
+ break;
+ case ThreadType::kDisplayCritical: {
+ priority = ThreadPriorityForTest::kDisplay;
+ pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
+ break;
+ }
+ case ThreadType::kRealtimeAudio:
+ priority = ThreadPriorityForTest::kRealtimeAudio;
+ SetPriorityRealtimeAudio(GetCurrentThreadRealtimePeriod());
DCHECK_EQ([[NSThread currentThread] threadPriority], 1.0);
break;
}
- [[[NSThread currentThread] threadDictionary]
- setObject:@(static_cast<int>(priority))
- forKey:kThreadPriorityKey];
+ [[NSThread currentThread] threadDictionary][kThreadPriorityForTestKey] =
+ @(static_cast<int>(priority));
}
+} // namespace internal
+
// static
-ThreadPriority PlatformThread::GetCurrentThreadPriority() {
- NSNumber* priority = base::mac::ObjCCast<NSNumber>([[[NSThread currentThread]
- threadDictionary] objectForKey:kThreadPriorityKey]);
+ThreadPriorityForTest PlatformThread::GetCurrentThreadPriorityForTest() {
+ NSNumber* priority = base::mac::ObjCCast<NSNumber>(
+ [[NSThread currentThread] threadDictionary][kThreadPriorityForTestKey]);
if (!priority)
- return ThreadPriority::NORMAL;
+ return ThreadPriorityForTest::kNormal;
- ThreadPriority thread_priority =
- static_cast<ThreadPriority>(priority.intValue);
- switch (thread_priority) {
- case ThreadPriority::BACKGROUND:
- case ThreadPriority::NORMAL:
- case ThreadPriority::DISPLAY:
- case ThreadPriority::REALTIME_AUDIO:
- return thread_priority;
- default:
- NOTREACHED() << "Unknown priority.";
- return ThreadPriority::NORMAL;
- }
+ ThreadPriorityForTest thread_priority =
+ static_cast<ThreadPriorityForTest>(priority.intValue);
+ DCHECK_GE(thread_priority, ThreadPriorityForTest::kBackground);
+ DCHECK_LE(thread_priority, ThreadPriorityForTest::kMaxValue);
+ return thread_priority;
}
size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) {
-#if defined(OS_IOS)
+#if BUILDFLAG(IS_IOS)
+#if BUILDFLAG(USE_BLINK)
+ // For iOS 512kB (the default) isn't sufficient, but using the code
+ // for Mac OS X below will return 8MB. So just be a little more conservative
+ // and return 1MB for now.
+ return 1024 * 1024;
+#else
return 0;
+#endif
#else
// The Mac OS X default for a pthread stack size is 512kB.
// Libc-594.1.4/pthreads/pthread.c's pthread_attr_init uses
diff --git a/base/threading/platform_thread_posix.cc b/base/threading/platform_thread_posix.cc
index 829d0db..6743b44 100644
--- a/base/threading/platform_thread_posix.cc
+++ b/base/threading/platform_thread_posix.cc
@@ -1,42 +1,53 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/notreached.h"
#include "base/threading/platform_thread.h"
#include <errno.h>
#include <pthread.h>
#include <sched.h>
+#include <stddef.h>
+#include <stdint.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
+#include <tuple>
-#include "starboard/types.h"
-
-#include "base/debug/activity_tracker.h"
+#include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
+#include "base/compiler_specific.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
+#include "base/memory/raw_ptr.h"
#include "base/threading/platform_thread_internal_posix.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_id_name_manager.h"
+#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
-#if !defined(OS_MACOSX) && !defined(OS_FUCHSIA) && !defined(OS_NACL)
+#if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_FUCHSIA) && !BUILDFLAG(IS_NACL)
#include "base/posix/can_lower_nice_to.h"
#endif
-#if defined(OS_LINUX)
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#include <sys/syscall.h>
+#include <atomic>
#endif
-#if defined(OS_FUCHSIA)
+#if BUILDFLAG(IS_FUCHSIA)
#include <zircon/process.h>
#else
#include <sys/resource.h>
#endif
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
+#include "base/allocator/partition_allocator/starscan/pcscan.h"
+#include "base/allocator/partition_allocator/starscan/stack/stack.h"
+#endif
+
namespace base {
void InitThreading();
@@ -46,12 +57,12 @@
namespace {
struct ThreadParams {
- ThreadParams()
- : delegate(nullptr), joinable(false), priority(ThreadPriority::NORMAL) {}
+ ThreadParams() = default;
- PlatformThread::Delegate* delegate;
- bool joinable;
- ThreadPriority priority;
+ raw_ptr<PlatformThread::Delegate> delegate = nullptr;
+ bool joinable = false;
+ ThreadType thread_type = ThreadType::kDefault;
+ MessagePumpType message_pump_type = MessagePumpType::DEFAULT;
};
void* ThreadFunc(void* params) {
@@ -63,14 +74,24 @@
delegate = thread_params->delegate;
if (!thread_params->joinable)
- base::ThreadRestrictions::SetSingletonAllowed(false);
+ base::DisallowSingleton();
-#if !defined(OS_NACL)
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
+ partition_alloc::internal::PCScan::NotifyThreadCreated(
+ partition_alloc::internal::GetStackPointer());
+#endif
+
+#if !BUILDFLAG(IS_NACL)
+#if BUILDFLAG(IS_APPLE)
+ PlatformThread::SetCurrentThreadRealtimePeriodValue(
+ delegate->GetRealtimePeriod());
+#endif
+
// Threads on linux/android may inherit their priority from the thread
// where they were created. This explicitly sets the priority of all new
// threads.
- PlatformThread::SetCurrentThreadPriority(thread_params->priority);
-#endif
+ PlatformThread::SetCurrentThreadType(thread_params->thread_type);
+#endif // !BUILDFLAG(IS_NACL)
}
ThreadIdNameManager::GetInstance()->RegisterThread(
@@ -83,6 +104,10 @@
PlatformThread::CurrentHandle().platform_handle(),
PlatformThread::CurrentId());
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
+ partition_alloc::internal::PCScan::NotifyThreadDestroyed();
+#endif
+
base::TerminateOnThread();
return nullptr;
}
@@ -91,7 +116,8 @@
bool joinable,
PlatformThread::Delegate* delegate,
PlatformThreadHandle* thread_handle,
- ThreadPriority priority) {
+ ThreadType thread_type,
+ MessagePumpType message_pump_type) {
DCHECK(thread_handle);
base::InitThreading();
@@ -113,14 +139,15 @@
std::unique_ptr<ThreadParams> params(new ThreadParams);
params->delegate = delegate;
params->joinable = joinable;
- params->priority = priority;
+ params->thread_type = thread_type;
+ params->message_pump_type = message_pump_type;
pthread_t handle;
int err = pthread_create(&handle, &attributes, ThreadFunc, params.get());
bool success = !err;
if (success) {
// ThreadParams should be deleted on the created thread after used.
- ignore_result(params.release());
+ std::ignore = params.release();
} else {
// Value of |handle| is undefined if pthread_create fails.
handle = 0;
@@ -134,30 +161,109 @@
return success;
}
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
+// Store the thread ids in local storage since calling the SWI can be
+// expensive and PlatformThread::CurrentId is used liberally.
+thread_local pid_t g_thread_id = -1;
+
+// A boolean value that indicates that the value stored in |g_thread_id| on the
+// main thread is invalid, because it hasn't been updated since the process
+// forked.
+//
+// This used to work by setting |g_thread_id| to -1 in a pthread_atfork handler.
+// However, when a multithreaded process forks, it is only allowed to call
+// async-signal-safe functions until it calls an exec() syscall. However,
+// accessing TLS may allocate (see crbug.com/1275748), which is not
+// async-signal-safe and therefore causes deadlocks, corruption, and crashes.
+//
+// It's Atomic to placate TSAN.
+std::atomic<bool> g_main_thread_tid_cache_valid = false;
+
+// Tracks whether the current thread is the main thread, and therefore whether
+// |g_main_thread_tid_cache_valid| is relevant for the current thread. This is
+// also updated by PlatformThread::CurrentId().
+thread_local bool g_is_main_thread = true;
+
+class InitAtFork {
+ public:
+ InitAtFork() {
+ pthread_atfork(nullptr, nullptr, internal::InvalidateTidCache);
+ }
+};
+
+#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
} // namespace
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
+namespace internal {
+
+void InvalidateTidCache() {
+ g_main_thread_tid_cache_valid.store(false, std::memory_order_relaxed);
+}
+
+} // namespace internal
+
+#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
// static
PlatformThreadId PlatformThread::CurrentId() {
// Pthreads doesn't have the concept of a thread ID, so we have to reach down
// into the kernel.
-#if defined(OS_MACOSX)
+#if BUILDFLAG(IS_APPLE)
return pthread_mach_thread_np(pthread_self());
-#elif defined(OS_LINUX)
- return syscall(__NR_gettid);
-#elif defined(OS_ANDROID)
+#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ // Workaround false-positive MSAN use-of-uninitialized-value on
+ // thread_local storage for loaded libraries:
+ // https://github.com/google/sanitizers/issues/1265
+ MSAN_UNPOISON(&g_thread_id, sizeof(pid_t));
+ MSAN_UNPOISON(&g_is_main_thread, sizeof(bool));
+ static InitAtFork init_at_fork;
+ if (g_thread_id == -1 ||
+ (g_is_main_thread &&
+ !g_main_thread_tid_cache_valid.load(std::memory_order_relaxed))) {
+ // Update the cached tid.
+ g_thread_id = static_cast<pid_t>(syscall(__NR_gettid));
+ // If this is the main thread, we can mark the tid_cache as valid.
+ // Otherwise, stop the current thread from always entering this slow path.
+ if (g_thread_id == getpid()) {
+ g_main_thread_tid_cache_valid.store(true, std::memory_order_relaxed);
+ } else {
+ g_is_main_thread = false;
+ }
+ } else {
+#if DCHECK_IS_ON()
+ if (g_thread_id != syscall(__NR_gettid)) {
+ RAW_LOG(
+ FATAL,
+ "Thread id stored in TLS is different from thread id returned by "
+ "the system. It is likely that the process was forked without going "
+ "through fork().");
+ }
+#endif
+ }
+ return g_thread_id;
+#elif BUILDFLAG(IS_ANDROID)
+ // Note: do not cache the return value inside a thread_local variable on
+ // Android (as above). The reasons are:
+ // - thread_local is slow on Android (goes through emutls)
+ // - gettid() is fast, since its return value is cached in pthread (in the
+ // thread control block of pthread). See gettid.c in bionic.
return gettid();
-#elif defined(OS_FUCHSIA)
+#elif BUILDFLAG(IS_FUCHSIA)
return zx_thread_self();
-#elif defined(OS_SOLARIS) || defined(OS_QNX)
+#elif BUILDFLAG(IS_SOLARIS) || BUILDFLAG(IS_QNX)
return pthread_self();
-#elif defined(OS_NACL) && defined(__GLIBC__)
+#elif BUILDFLAG(IS_NACL) && defined(__GLIBC__)
return pthread_self();
-#elif defined(OS_NACL) && !defined(__GLIBC__)
+#elif BUILDFLAG(IS_NACL) && !defined(__GLIBC__)
// Pointers are 32-bits in NaCl.
return reinterpret_cast<int32_t>(pthread_self());
-#elif defined(OS_POSIX) && defined(OS_AIX)
+#elif BUILDFLAG(IS_POSIX) && BUILDFLAG(IS_AIX)
return pthread_self();
-#elif defined(OS_POSIX) && !defined(OS_AIX)
+#elif BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_AIX)
return reinterpret_cast<int64_t>(pthread_self());
#endif
}
@@ -172,10 +278,12 @@
return PlatformThreadHandle(pthread_self());
}
+#if !BUILDFLAG(IS_APPLE)
// static
void PlatformThread::YieldCurrentThread() {
sched_yield();
}
+#endif // !BUILDFLAG(IS_APPLE)
// static
void PlatformThread::Sleep(TimeDelta duration) {
@@ -184,9 +292,9 @@
// Break the duration into seconds and nanoseconds.
// NOTE: TimeDelta's microseconds are int64s while timespec's
// nanoseconds are longs, so this unpacking must prevent overflow.
- sleep_time.tv_sec = duration.InSeconds();
- duration -= TimeDelta::FromSeconds(sleep_time.tv_sec);
- sleep_time.tv_nsec = duration.InMicroseconds() * 1000; // nanoseconds
+ sleep_time.tv_sec = static_cast<time_t>(duration.InSeconds());
+ duration -= Seconds(sleep_time.tv_sec);
+ sleep_time.tv_nsec = static_cast<long>(duration.InMicroseconds() * 1000);
while (nanosleep(&sleep_time, &remaining) == -1 && errno == EINTR)
sleep_time = remaining;
@@ -198,39 +306,39 @@
}
// static
-bool PlatformThread::CreateWithPriority(size_t stack_size, Delegate* delegate,
- PlatformThreadHandle* thread_handle,
- ThreadPriority priority) {
+bool PlatformThread::CreateWithType(size_t stack_size,
+ Delegate* delegate,
+ PlatformThreadHandle* thread_handle,
+ ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
return CreateThread(stack_size, true /* joinable thread */, delegate,
- thread_handle, priority);
+ thread_handle, thread_type, pump_type_hint);
}
// static
bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) {
- return CreateNonJoinableWithPriority(stack_size, delegate,
- ThreadPriority::NORMAL);
+ return CreateNonJoinableWithType(stack_size, delegate, ThreadType::kDefault);
}
// static
-bool PlatformThread::CreateNonJoinableWithPriority(size_t stack_size,
- Delegate* delegate,
- ThreadPriority priority) {
+bool PlatformThread::CreateNonJoinableWithType(size_t stack_size,
+ Delegate* delegate,
+ ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
PlatformThreadHandle unused;
bool result = CreateThread(stack_size, false /* non-joinable thread */,
- delegate, &unused, priority);
+ delegate, &unused, thread_type, pump_type_hint);
return result;
}
// static
void PlatformThread::Join(PlatformThreadHandle thread_handle) {
- // Record the event that this thread is blocking upon (for hang diagnosis).
- base::debug::ScopedThreadJoinActivity thread_activity(&thread_handle);
-
// Joining another thread may block the current thread for a long time, since
// the thread referred to by |thread_handle| may still be running long-lived /
// blocking tasks.
- base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
+ base::internal::ScopedBlockingCallWithBaseSyncPrimitives scoped_blocking_call(
+ FROM_HERE, base::BlockingType::MAY_BLOCK);
CHECK_EQ(0, pthread_join(thread_handle.platform_handle(), nullptr));
}
@@ -239,26 +347,35 @@
CHECK_EQ(0, pthread_detach(thread_handle.platform_handle()));
}
-// Mac and Fuchsia have their own Set/GetCurrentThreadPriority()
-// implementations.
-#if !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
+// Mac and Fuchsia have their own SetCurrentThreadType() and
+// GetCurrentThreadPriorityForTest() implementations.
+#if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_FUCHSIA)
// static
-bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
-#if defined(OS_NACL)
+bool PlatformThread::CanChangeThreadType(ThreadType from, ThreadType to) {
+#if BUILDFLAG(IS_NACL)
return false;
#else
- return internal::CanLowerNiceTo(
- internal::ThreadPriorityToNiceValue(priority));
-#endif // defined(OS_NACL)
+ if (from >= to) {
+ // Decreasing thread priority on POSIX is always allowed.
+ return true;
+ }
+ if (to == ThreadType::kRealtimeAudio) {
+ return internal::CanSetThreadTypeToRealtimeAudio();
+ }
+
+ return internal::CanLowerNiceTo(internal::ThreadTypeToNiceValue(to));
+#endif // BUILDFLAG(IS_NACL)
}
-// static
-void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) {
-#if defined(OS_NACL)
+namespace internal {
+
+void SetCurrentThreadTypeImpl(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
+#if BUILDFLAG(IS_NACL)
NOTIMPLEMENTED();
#else
- if (internal::SetCurrentThreadPriorityForPlatform(priority))
+ if (internal::SetCurrentThreadTypeForPlatform(thread_type, pump_type_hint))
return;
// setpriority(2) should change the whole thread group's (i.e. process)
@@ -267,41 +384,41 @@
// Linux/NPTL implementation of POSIX threads, the nice value is a per-thread
// attribute". Also, 0 is prefered to the current thread id since it is
// equivalent but makes sandboxing easier (https://crbug.com/399473).
- const int nice_setting = internal::ThreadPriorityToNiceValue(priority);
+ const int nice_setting = internal::ThreadTypeToNiceValue(thread_type);
if (setpriority(PRIO_PROCESS, 0, nice_setting)) {
DVPLOG(1) << "Failed to set nice value of thread ("
<< PlatformThread::CurrentId() << ") to " << nice_setting;
}
-#endif // defined(OS_NACL)
+#endif // BUILDFLAG(IS_NACL)
}
+} // namespace internal
+
// static
-ThreadPriority PlatformThread::GetCurrentThreadPriority() {
-#if defined(OS_NACL)
+ThreadPriorityForTest PlatformThread::GetCurrentThreadPriorityForTest() {
+#if BUILDFLAG(IS_NACL)
NOTIMPLEMENTED();
- return ThreadPriority::NORMAL;
+ return ThreadPriorityForTest::kNormal;
#else
// Mirrors SetCurrentThreadPriority()'s implementation.
- ThreadPriority platform_specific_priority;
- if (internal::GetCurrentThreadPriorityForPlatform(
- &platform_specific_priority)) {
- return platform_specific_priority;
- }
+ auto platform_specific_priority =
+ internal::GetCurrentThreadPriorityForPlatformForTest(); // IN-TEST
+ if (platform_specific_priority)
+ return platform_specific_priority.value();
- // Need to clear errno before calling getpriority():
- // http://man7.org/linux/man-pages/man2/getpriority.2.html
- errno = 0;
- int nice_value = getpriority(PRIO_PROCESS, 0);
- if (errno != 0) {
- DVPLOG(1) << "Failed to get nice value of thread ("
- << PlatformThread::CurrentId() << ")";
- return ThreadPriority::NORMAL;
- }
+ int nice_value = internal::GetCurrentThreadNiceValue();
- return internal::NiceValueToThreadPriority(nice_value);
-#endif // !defined(OS_NACL)
+ return internal::NiceValueToThreadPriorityForTest(nice_value); // IN-TEST
+#endif // !BUILDFLAG(IS_NACL)
}
-#endif // !defined(OS_MACOSX) && !defined(OS_FUCHSIA)
+#endif // !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_FUCHSIA)
+
+// static
+size_t PlatformThread::GetDefaultThreadStackSize() {
+ pthread_attr_t attributes;
+ pthread_attr_init(&attributes);
+ return base::GetDefaultThreadStackSize(attributes);
+}
} // namespace base
diff --git a/base/threading/platform_thread_ref.cc b/base/threading/platform_thread_ref.cc
new file mode 100644
index 0000000..2771d59
--- /dev/null
+++ b/base/threading/platform_thread_ref.cc
@@ -0,0 +1,16 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/platform_thread_ref.h"
+
+#include <ostream>
+
+namespace base {
+
+std::ostream& operator<<(std::ostream& os, const PlatformThreadRef& ref) {
+ os << ref.id_;
+ return os;
+}
+
+} // namespace base
diff --git a/base/threading/platform_thread_ref.h b/base/threading/platform_thread_ref.h
new file mode 100644
index 0000000..04142c4
--- /dev/null
+++ b/base/threading/platform_thread_ref.h
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// WARNING: *DO NOT* use this class directly. base::PlatformThreadRef is a
+// low-level platform-specific abstraction to the OS's threading interface.
+// Instead, consider using a message-loop driven base::Thread, see
+// base/threading/thread.h.
+
+#ifndef BASE_THREADING_PLATFORM_THREAD_REF_H_
+#define BASE_THREADING_PLATFORM_THREAD_REF_H_
+
+#include <iosfwd>
+
+#include "base/base_export.h"
+#include "build/build_config.h"
+
+#if defined(STARBOARD)
+#include <pthread.h>
+#include "starboard/thread.h"
+#elif BUILDFLAG(IS_WIN)
+#include "base/win/windows_types.h"
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+#include <pthread.h>
+#endif
+
+namespace base {
+
+// Used for thread checking and debugging.
+// Meant to be as fast as possible.
+// These are produced by PlatformThread::CurrentRef(), and used to later
+// check if we are on the same thread or not by using ==. These are safe
+// to copy between threads, but can't be copied to another process as they
+// have no meaning there. Also, the internal identifier can be re-used
+// after a thread dies, so a PlatformThreadRef cannot be reliably used
+// to distinguish a new thread from an old, dead thread.
+class PlatformThreadRef {
+ public:
+#if defined(STARBOARD)
+#if SB_API_VERSION < 16
+ typedef SbThread RefType;
+#else
+ using RefType = pthread_t;
+#endif
+#elif BUILDFLAG(IS_WIN)
+ using RefType = DWORD;
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+ using RefType = pthread_t;
+#endif
+
+ constexpr PlatformThreadRef() = default;
+ explicit constexpr PlatformThreadRef(RefType id) : id_(id) {}
+
+ bool operator==(PlatformThreadRef other) const { return id_ == other.id_; }
+ bool operator!=(PlatformThreadRef other) const { return id_ != other.id_; }
+
+ bool is_null() const { return id_ == 0; }
+
+ private:
+ friend BASE_EXPORT std::ostream& operator<<(std::ostream& os,
+ const PlatformThreadRef& ref);
+
+ RefType id_ = 0;
+};
+
+BASE_EXPORT std::ostream& operator<<(std::ostream& os,
+ const PlatformThreadRef& ref);
+
+} // namespace base
+
+#endif // BASE_THREADING_PLATFORM_THREAD_REF_H_
diff --git a/base/threading/platform_thread_starboard.cc b/base/threading/platform_thread_starboard.cc
index 4f26ede..594ede6 100644
--- a/base/threading/platform_thread_starboard.cc
+++ b/base/threading/platform_thread_starboard.cc
@@ -14,9 +14,14 @@
#include "base/threading/platform_thread.h"
+#include <pthread.h>
+#include <sched.h>
+#include <unistd.h>
+
#include "base/logging.h"
#include "base/threading/thread_id_name_manager.h"
#include "base/threading/thread_restrictions.h"
+#include "starboard/configuration_constants.h"
#include "starboard/thread.h"
namespace base {
@@ -26,13 +31,24 @@
struct ThreadParams {
PlatformThread::Delegate* delegate;
bool joinable;
+ SbThreadPriority thread_priority;
+ std::string thread_name;
};
void* ThreadFunc(void* params) {
ThreadParams* thread_params = static_cast<ThreadParams*>(params);
PlatformThread::Delegate* delegate = thread_params->delegate;
+
+#if SB_API_VERSION >= 16
+ if (kSbHasThreadPrioritySupport) {
+ SbThreadSetPriority(thread_params->thread_priority);
+ }
+#endif // SB_API_VERSION >= 16
+ pthread_setname_np(pthread_self(), thread_params->thread_name.c_str());
+
+ absl::optional<ScopedDisallowSingleton> disallow_singleton;
if (!thread_params->joinable) {
- base::ThreadRestrictions::SetSingletonAllowed(false);
+ disallow_singleton.emplace();
}
delete thread_params;
@@ -50,9 +66,49 @@
return NULL;
}
+#if SB_API_VERSION >= 16
bool CreateThread(size_t stack_size,
SbThreadPriority priority,
- SbThreadAffinity affinity,
+ bool joinable,
+ const char* name,
+ PlatformThread::Delegate* delegate,
+ PlatformThreadHandle* thread_handle) {
+ ThreadParams* params = new ThreadParams;
+ params->delegate = delegate;
+ params->joinable = joinable;
+ params->thread_priority = priority;
+ if (name != nullptr) {
+ params->thread_name = name;
+ }
+
+ pthread_attr_t attr;
+ if (pthread_attr_init(&attr) != 0) {
+ return false;
+ }
+
+ pthread_attr_setstacksize(&attr, stack_size);
+
+ if (!joinable) {
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ }
+
+ pthread_t thread = 0;
+ pthread_create(&thread, &attr, ThreadFunc, params);
+ pthread_attr_destroy(&attr);
+
+ if (thread != 0) {
+ if (thread_handle) {
+ *thread_handle = PlatformThreadHandle(thread);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+#else
+bool CreateThread(size_t stack_size,
+ SbThreadPriority priority,
bool joinable,
const char* name,
PlatformThread::Delegate* delegate,
@@ -61,7 +117,7 @@
params->delegate = delegate;
params->joinable = joinable;
- SbThread thread = SbThreadCreate(stack_size, priority, affinity, joinable,
+ SbThread thread = SbThreadCreate(stack_size, priority, kSbThreadNoAffinity, joinable,
name, ThreadFunc, params);
if (SbThreadIsValid(thread)) {
if (thread_handle) {
@@ -73,9 +129,26 @@
return false;
}
+#endif // SB_API_VERSION >= 16
-inline SbThreadPriority toSbPriority(ThreadPriority priority) {
- return static_cast<SbThreadPriority>(priority);
+inline SbThreadPriority toSbPriority(ThreadType priority) {
+ switch (priority) {
+ case ThreadType::kBackground:
+ return kSbThreadPriorityLowest;
+ case ThreadType::kUtility:
+ return kSbThreadPriorityLow;
+ case ThreadType::kResourceEfficient:
+ return kSbThreadPriorityNormal;
+ case ThreadType::kDefault:
+ return kSbThreadNoPriority;
+ case ThreadType::kCompositing:
+ return kSbThreadPriorityHigh;
+ case ThreadType::kDisplayCritical:
+ return kSbThreadPriorityHighest;
+ case ThreadType::kRealtimeAudio:
+ return kSbThreadPriorityRealTime;
+ };
+ NOTREACHED();
}
} // namespace
@@ -86,28 +159,39 @@
// static
PlatformThreadRef PlatformThread::CurrentRef() {
+#if SB_API_VERSION < 16
return PlatformThreadRef(SbThreadGetCurrent());
+#else
+ return PlatformThreadRef(pthread_self());
+#endif // SB_API_VERSION < 16
+
}
// static
PlatformThreadHandle PlatformThread::CurrentHandle() {
+#if SB_API_VERSION < 16
return PlatformThreadHandle(SbThreadGetCurrent());
+#else
+ return PlatformThreadHandle(pthread_self());
+#endif // SB_API_VERSION < 16
}
// static
void PlatformThread::YieldCurrentThread() {
- SbThreadYield();
+ sched_yield();
}
// static
void PlatformThread::Sleep(TimeDelta duration) {
- SbThreadSleep(duration.InMicroseconds());
+ usleep(duration.InMicroseconds());
}
// static
void PlatformThread::SetName(const std::string& name) {
ThreadIdNameManager::GetInstance()->SetName(name);
- SbThreadSetName(name.c_str());
+
+ std::string buffer(name, 0, kSbMaxThreadNameLength - 1);
+ pthread_setname_np(pthread_self(), buffer.c_str());
}
// static
@@ -116,20 +200,22 @@
}
// static
-bool PlatformThread::CreateWithPriority(size_t stack_size,
+bool PlatformThread::CreateWithType(size_t stack_size,
Delegate* delegate,
PlatformThreadHandle* thread_handle,
- ThreadPriority priority) {
- return CreateThread(stack_size, toSbPriority(priority), kSbThreadNoAffinity,
+ ThreadType priority,
+ MessagePumpType /* pump_type_hint */) {
+ return CreateThread(stack_size, toSbPriority(priority),
true /* joinable thread */, NULL, delegate,
thread_handle);
}
// static
-bool PlatformThread::CreateNonJoinableWithPriority(size_t stack_size,
+bool PlatformThread::CreateNonJoinableWithType(size_t stack_size,
Delegate* delegate,
- ThreadPriority priority) {
- return CreateThread(stack_size, toSbPriority(priority), kSbThreadNoAffinity,
+ ThreadType priority,
+ MessagePumpType /* pump_type_hint */) {
+ return CreateThread(stack_size, toSbPriority(priority),
false /* joinable thread */, NULL, delegate, NULL);
}
@@ -138,26 +224,39 @@
// Joining another thread may block the current thread for a long time, since
// the thread referred to by |thread_handle| may still be running long-lived /
// blocking tasks.
- AssertBlockingAllowed();
+ internal::AssertBlockingAllowed();
+#if SB_API_VERSION < 16
SbThreadJoin(thread_handle.platform_handle(), NULL);
+#else
+ pthread_join(thread_handle.platform_handle(), NULL);
+#endif // SB_API_VERSION < 16
}
void PlatformThread::Detach(PlatformThreadHandle thread_handle) {
+#if SB_API_VERSION < 16
SbThreadDetach(thread_handle.platform_handle());
+#else
+ pthread_detach(thread_handle.platform_handle());
+#endif // SB_API_VERSION < 16
}
-void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) {
+void internal::SetCurrentThreadTypeImpl(ThreadType /* thread_type */, MessagePumpType /*pump_type_hint*/) {
NOTIMPLEMENTED();
}
-ThreadPriority PlatformThread::GetCurrentThreadPriority() {
- NOTIMPLEMENTED();
- return ThreadPriority::NORMAL;
-}
-
// static
-bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
+bool PlatformThread::CanChangeThreadType(ThreadType /* from */, ThreadType /* to */) {
return false;
}
+size_t PlatformThread::GetDefaultThreadStackSize() {
+ return 0;
+}
+
+// static
+ThreadPriorityForTest PlatformThread::GetCurrentThreadPriorityForTest() {
+ NOTIMPLEMENTED();
+ return ThreadPriorityForTest::kNormal;
+}
+
} // namespace base
diff --git a/base/threading/platform_thread_unittest.cc b/base/threading/platform_thread_unittest.cc
index 9e530e4..e504bcf 100644
--- a/base/threading/platform_thread_unittest.cc
+++ b/base/threading/platform_thread_unittest.cc
@@ -1,21 +1,43 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/threading/platform_thread.h"
+
+#include <stddef.h>
+
#include "base/compiler_specific.h"
-#include "base/macros.h"
+#include "base/process/process.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/scoped_feature_list.h"
-#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/threading/threading_features.h"
+#include "build/blink_buildflags.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if defined(OS_POSIX)
+#if BUILDFLAG(IS_POSIX)
#include "base/threading/platform_thread_internal_posix.h"
-#elif defined(OS_WIN)
+#elif BUILDFLAG(IS_WIN)
#include <windows.h>
#include "base/threading/platform_thread_win.h"
-#include "starboard/types.h"
+#endif
+
+#if BUILDFLAG(IS_APPLE)
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <mach/thread_policy.h>
+#include "base/mac/mac_util.h"
+#include "base/metrics/field_trial_params.h"
+#include "base/time/time.h"
+#endif
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include <pthread.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
#endif
namespace base {
@@ -29,14 +51,15 @@
TrivialThread() : run_event_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED) {}
+ TrivialThread(const TrivialThread&) = delete;
+ TrivialThread& operator=(const TrivialThread&) = delete;
+
void ThreadMain() override { run_event_.Signal(); }
WaitableEvent& run_event() { return run_event_; }
private:
WaitableEvent run_event_;
-
- DISALLOW_COPY_AND_ASSIGN(TrivialThread);
};
} // namespace
@@ -53,16 +76,16 @@
TEST(PlatformThreadTest, TrivialJoinTimesTen) {
TrivialThread thread[10];
- PlatformThreadHandle handle[arraysize(thread)];
+ PlatformThreadHandle handle[std::size(thread)];
- for (size_t n = 0; n < arraysize(thread); n++)
- ASSERT_FALSE(thread[n].run_event().IsSignaled());
- for (size_t n = 0; n < arraysize(thread); n++)
+ for (auto& n : thread)
+ ASSERT_FALSE(n.run_event().IsSignaled());
+ for (size_t n = 0; n < std::size(thread); n++)
ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n]));
- for (size_t n = 0; n < arraysize(thread); n++)
- PlatformThread::Join(handle[n]);
- for (size_t n = 0; n < arraysize(thread); n++)
- ASSERT_TRUE(thread[n].run_event().IsSignaled());
+ for (auto n : handle)
+ PlatformThread::Join(n);
+ for (auto& n : thread)
+ ASSERT_TRUE(n.run_event().IsSignaled());
}
// The following detach tests are by nature racy. The run_event approximates the
@@ -80,16 +103,16 @@
TEST(PlatformThreadTest, TrivialDetachTimesTen) {
TrivialThread thread[10];
- PlatformThreadHandle handle[arraysize(thread)];
+ PlatformThreadHandle handle[std::size(thread)];
- for (size_t n = 0; n < arraysize(thread); n++)
- ASSERT_FALSE(thread[n].run_event().IsSignaled());
- for (size_t n = 0; n < arraysize(thread); n++) {
+ for (auto& n : thread)
+ ASSERT_FALSE(n.run_event().IsSignaled());
+ for (size_t n = 0; n < std::size(thread); n++) {
ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n]));
PlatformThread::Detach(handle[n]);
}
- for (size_t n = 0; n < arraysize(thread); n++)
- thread[n].run_event().Wait();
+ for (auto& n : thread)
+ n.run_event().Wait();
}
// Tests of basic thread functions ---------------------------------------------
@@ -105,6 +128,10 @@
terminate_thread_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED),
done_(false) {}
+
+ FunctionTestThread(const FunctionTestThread&) = delete;
+ FunctionTestThread& operator=(const FunctionTestThread&) = delete;
+
~FunctionTestThread() override {
EXPECT_TRUE(terminate_thread_.IsSignaled())
<< "Need to mark thread for termination and join the underlying thread "
@@ -156,8 +183,6 @@
mutable WaitableEvent termination_ready_;
WaitableEvent terminate_thread_;
bool done_;
-
- DISALLOW_COPY_AND_ASSIGN(FunctionTestThread);
};
} // namespace
@@ -186,17 +211,17 @@
PlatformThreadId main_thread_id = PlatformThread::CurrentId();
FunctionTestThread thread[10];
- PlatformThreadHandle handle[arraysize(thread)];
+ PlatformThreadHandle handle[std::size(thread)];
- for (size_t n = 0; n < arraysize(thread); n++)
- ASSERT_FALSE(thread[n].IsRunning());
+ for (const auto& n : thread)
+ ASSERT_FALSE(n.IsRunning());
- for (size_t n = 0; n < arraysize(thread); n++)
+ for (size_t n = 0; n < std::size(thread); n++)
ASSERT_TRUE(PlatformThread::Create(0, &thread[n], &handle[n]));
- for (size_t n = 0; n < arraysize(thread); n++)
- thread[n].WaitForTerminationReady();
+ for (auto& n : thread)
+ n.WaitForTerminationReady();
- for (size_t n = 0; n < arraysize(thread); n++) {
+ for (size_t n = 0; n < std::size(thread); n++) {
ASSERT_TRUE(thread[n].IsRunning());
EXPECT_NE(thread[n].thread_id(), main_thread_id);
@@ -206,158 +231,305 @@
}
}
- for (size_t n = 0; n < arraysize(thread); n++)
- thread[n].MarkForTermination();
- for (size_t n = 0; n < arraysize(thread); n++)
- PlatformThread::Join(handle[n]);
- for (size_t n = 0; n < arraysize(thread); n++)
- ASSERT_FALSE(thread[n].IsRunning());
+ for (auto& n : thread)
+ n.MarkForTermination();
+ for (auto n : handle)
+ PlatformThread::Join(n);
+ for (const auto& n : thread)
+ ASSERT_FALSE(n.IsRunning());
// Make sure that the thread ID is the same across calls.
EXPECT_EQ(main_thread_id, PlatformThread::CurrentId());
}
-#if !defined(STARBOARD)
namespace {
-class ThreadPriorityTestThread : public FunctionTestThread {
+constexpr ThreadType kAllThreadTypes[] = {
+ ThreadType::kRealtimeAudio, ThreadType::kDisplayCritical,
+ ThreadType::kCompositing, ThreadType::kDefault,
+ ThreadType::kResourceEfficient, ThreadType::kUtility,
+ ThreadType::kBackground};
+
+class ThreadTypeTestThread : public FunctionTestThread {
public:
- explicit ThreadPriorityTestThread(ThreadPriority from, ThreadPriority to)
+ explicit ThreadTypeTestThread(ThreadType from, ThreadType to)
: from_(from), to_(to) {}
- ~ThreadPriorityTestThread() override = default;
+
+ ThreadTypeTestThread(const ThreadTypeTestThread&) = delete;
+ ThreadTypeTestThread& operator=(const ThreadTypeTestThread&) = delete;
+
+ ~ThreadTypeTestThread() override = default;
private:
void RunTest() override {
- EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(),
- ThreadPriority::NORMAL);
- PlatformThread::SetCurrentThreadPriority(from_);
- EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(), from_);
- PlatformThread::SetCurrentThreadPriority(to_);
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault);
+ PlatformThread::SetCurrentThreadType(from_);
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(), from_);
+ PlatformThread::SetCurrentThreadType(to_);
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(), to_);
+ }
- if (static_cast<int>(to_) <= static_cast<int>(from_) ||
- PlatformThread::CanIncreaseThreadPriority(to_)) {
- EXPECT_EQ(PlatformThread::GetCurrentThreadPriority(), to_);
- } else {
- EXPECT_NE(PlatformThread::GetCurrentThreadPriority(), to_);
+ const ThreadType from_;
+ const ThreadType to_;
+};
+
+class ThreadPriorityTestThread : public FunctionTestThread {
+ public:
+ ThreadPriorityTestThread(ThreadType thread_type,
+ ThreadPriorityForTest priority)
+ : thread_type_(thread_type), priority(priority) {}
+
+ private:
+ void RunTest() override {
+ testing::Message message;
+ message << "thread_type: " << static_cast<int>(thread_type_);
+ SCOPED_TRACE(message);
+
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(), ThreadType::kDefault);
+ PlatformThread::SetCurrentThreadType(thread_type_);
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(), thread_type_);
+ if (PlatformThread::CanChangeThreadType(ThreadType::kDefault,
+ thread_type_)) {
+ EXPECT_EQ(PlatformThread::GetCurrentThreadPriorityForTest(), priority);
}
}
- const ThreadPriority from_;
- const ThreadPriority to_;
-
- DISALLOW_COPY_AND_ASSIGN(ThreadPriorityTestThread);
+ const ThreadType thread_type_;
+ const ThreadPriorityForTest priority;
};
-void TestSetCurrentThreadPriority() {
- constexpr ThreadPriority kAllThreadPriorities[] = {
- ThreadPriority::REALTIME_AUDIO, ThreadPriority::DISPLAY,
- ThreadPriority::NORMAL, ThreadPriority::BACKGROUND};
-
- for (auto from : kAllThreadPriorities) {
- if (static_cast<int>(from) <= static_cast<int>(ThreadPriority::NORMAL) ||
- PlatformThread::CanIncreaseThreadPriority(from)) {
- for (auto to : kAllThreadPriorities) {
- ThreadPriorityTestThread thread(from, to);
- PlatformThreadHandle handle;
-
- ASSERT_FALSE(thread.IsRunning());
- ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
- thread.WaitForTerminationReady();
- ASSERT_TRUE(thread.IsRunning());
-
- thread.MarkForTermination();
- PlatformThread::Join(handle);
- ASSERT_FALSE(thread.IsRunning());
- }
+void TestSetCurrentThreadType() {
+ for (auto from : kAllThreadTypes) {
+ if (!PlatformThread::CanChangeThreadType(ThreadType::kDefault, from)) {
+ continue;
}
+ for (auto to : kAllThreadTypes) {
+ ThreadTypeTestThread thread(from, to);
+ PlatformThreadHandle handle;
+
+ ASSERT_FALSE(thread.IsRunning());
+ ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
+ thread.WaitForTerminationReady();
+ ASSERT_TRUE(thread.IsRunning());
+
+ thread.MarkForTermination();
+ PlatformThread::Join(handle);
+ ASSERT_FALSE(thread.IsRunning());
+ }
+ }
+}
+
+void TestPriorityResultingFromThreadType(ThreadType thread_type,
+ ThreadPriorityForTest priority) {
+ ThreadPriorityTestThread thread(thread_type, priority);
+ PlatformThreadHandle handle;
+
+ ASSERT_FALSE(thread.IsRunning());
+ ASSERT_TRUE(PlatformThread::Create(0, &thread, &handle));
+ thread.WaitForTerminationReady();
+ ASSERT_TRUE(thread.IsRunning());
+
+ thread.MarkForTermination();
+ PlatformThread::Join(handle);
+ ASSERT_FALSE(thread.IsRunning());
+}
+
+ThreadPriorityForTest GetCurrentThreadPriorityIfStartWithThreadType(
+ ThreadType thread_type,
+ MessagePumpType message_pump_type) {
+ Thread::Options options;
+ options.thread_type = thread_type;
+ options.message_pump_type = message_pump_type;
+
+ Thread thread("GetCurrentThreadPriorityIfStartWithThreadType");
+ thread.StartWithOptions(std::move(options));
+ thread.WaitUntilThreadStarted();
+
+ ThreadPriorityForTest priority;
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindOnce(
+ [](ThreadPriorityForTest* priority) {
+ *priority =
+ PlatformThread::GetCurrentThreadPriorityForTest();
+ },
+ &priority));
+ thread.Stop();
+
+ return priority;
+}
+
+ThreadPriorityForTest GetCurrentThreadPriorityIfSetThreadTypeLater(
+ ThreadType thread_type,
+ MessagePumpType message_pump_type) {
+ Thread::Options options;
+ options.message_pump_type = message_pump_type;
+
+ Thread thread("GetCurrentThreadPriorityIfSetThreadTypeLater");
+ thread.StartWithOptions(std::move(options));
+ thread.WaitUntilThreadStarted();
+
+ ThreadPriorityForTest priority;
+ thread.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](ThreadType thread_type, ThreadPriorityForTest* priority) {
+ PlatformThread::SetCurrentThreadType(thread_type);
+ *priority = PlatformThread::GetCurrentThreadPriorityForTest();
+ },
+ thread_type, &priority));
+ thread.Stop();
+
+ return priority;
+}
+
+void TestPriorityResultingFromThreadType(ThreadType thread_type,
+ MessagePumpType message_pump_type,
+ ThreadPriorityForTest priority) {
+ testing::Message message;
+ message << "thread_type: " << static_cast<int>(thread_type)
+ << ", message_pump_type: " << static_cast<int>(message_pump_type);
+ SCOPED_TRACE(message);
+
+ if (PlatformThread::CanChangeThreadType(ThreadType::kDefault, thread_type)) {
+ EXPECT_EQ(GetCurrentThreadPriorityIfStartWithThreadType(thread_type,
+ message_pump_type),
+ priority);
+ EXPECT_EQ(GetCurrentThreadPriorityIfSetThreadTypeLater(thread_type,
+ message_pump_type),
+ priority);
}
}
} // namespace
-// Test changing a created thread's priority.
-#if defined(OS_FUCHSIA)
-// TODO(crbug.com/851759): Thread priorities are not implemented in Fuchsia.
-#define MAYBE_SetCurrentThreadPriority DISABLED_SetCurrentThreadPriority
+// Test changing a created thread's type.
+TEST(PlatformThreadTest, SetCurrentThreadType) {
+ TestSetCurrentThreadType();
+}
+
+#if BUILDFLAG(IS_WIN)
+// Test changing a created thread's priority in an IDLE_PRIORITY_CLASS process
+// (regression test for https://crbug.com/901483).
+TEST(PlatformThreadTest,
+ SetCurrentThreadTypeWithThreadModeBackgroundIdleProcess) {
+ ::SetPriorityClass(Process::Current().Handle(), IDLE_PRIORITY_CLASS);
+ TestSetCurrentThreadType();
+ ::SetPriorityClass(Process::Current().Handle(), NORMAL_PRIORITY_CLASS);
+}
+#endif // BUILDFLAG(IS_WIN)
+
+#if !defined(STARBOARD)
+// Ideally PlatformThread::CanChangeThreadType() would be true on all
+// platforms for all priorities. This not being the case. This test documents
+// and hardcodes what we know. Please inform scheduler-dev@chromium.org if this
+// proprerty changes for a given platform.
+TEST(PlatformThreadTest, CanChangeThreadType) {
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+ // On Ubuntu, RLIMIT_NICE and RLIMIT_RTPRIO are 0 by default, so we won't be
+ // able to increase priority to any level.
+ constexpr bool kCanIncreasePriority = false;
#else
-#define MAYBE_SetCurrentThreadPriority SetCurrentThreadPriority
+ constexpr bool kCanIncreasePriority = true;
#endif
-TEST(PlatformThreadTest, MAYBE_SetCurrentThreadPriority) {
- TestSetCurrentThreadPriority();
+
+ for (auto type : kAllThreadTypes) {
+ EXPECT_TRUE(PlatformThread::CanChangeThreadType(type, type));
+ }
+#if BUILDFLAG(IS_FUCHSIA)
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kUtility));
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(
+ ThreadType::kBackground, ThreadType::kResourceEfficient));
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kDefault));
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kCompositing));
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kDefault,
+ ThreadType::kBackground));
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kCompositing,
+ ThreadType::kBackground));
+#else
+ EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kUtility),
+ kCanIncreasePriority);
+ EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kResourceEfficient),
+ kCanIncreasePriority);
+ EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kDefault),
+ kCanIncreasePriority);
+ EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kCompositing),
+ kCanIncreasePriority);
+ EXPECT_TRUE(PlatformThread::CanChangeThreadType(ThreadType::kDefault,
+ ThreadType::kBackground));
+ EXPECT_TRUE(PlatformThread::CanChangeThreadType(ThreadType::kCompositing,
+ ThreadType::kBackground));
+#endif
+ EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kDisplayCritical),
+ kCanIncreasePriority);
+ EXPECT_EQ(PlatformThread::CanChangeThreadType(ThreadType::kBackground,
+ ThreadType::kRealtimeAudio),
+ kCanIncreasePriority);
+#if BUILDFLAG(IS_FUCHSIA)
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kDisplayCritical,
+ ThreadType::kBackground));
+ EXPECT_FALSE(PlatformThread::CanChangeThreadType(ThreadType::kRealtimeAudio,
+ ThreadType::kBackground));
+#else
+ EXPECT_TRUE(PlatformThread::CanChangeThreadType(ThreadType::kDisplayCritical,
+ ThreadType::kBackground));
+ EXPECT_TRUE(PlatformThread::CanChangeThreadType(ThreadType::kRealtimeAudio,
+ ThreadType::kBackground));
+#endif
}
+#endif // !defined(STARBOARD)
-#if defined(OS_WIN)
-// Test changing a created thread's priority, with the
-// kWindowsThreadModeBackground feature enabled.
-TEST(PlatformThreadTest, SetCurrentThreadPriorityWithThreadModeBackground) {
- test::ScopedFeatureList scoped_feature_list;
- scoped_feature_list.InitAndEnableFeature(
- features::kWindowsThreadModeBackground);
- TestSetCurrentThreadPriority();
+TEST(PlatformThreadTest, SetCurrentThreadTypeTest) {
+ TestPriorityResultingFromThreadType(ThreadType::kBackground,
+ ThreadPriorityForTest::kBackground);
+ TestPriorityResultingFromThreadType(ThreadType::kUtility,
+ ThreadPriorityForTest::kUtility);
+#if BUILDFLAG(IS_APPLE)
+ TestPriorityResultingFromThreadType(ThreadType::kResourceEfficient,
+ ThreadPriorityForTest::kUtility);
+#else
+ TestPriorityResultingFromThreadType(ThreadType::kResourceEfficient,
+ ThreadPriorityForTest::kNormal);
+#endif // BUILDFLAG(IS_APPLE)
+ TestPriorityResultingFromThreadType(ThreadType::kDefault,
+ ThreadPriorityForTest::kNormal);
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS)
+ TestPriorityResultingFromThreadType(ThreadType::kCompositing,
+ ThreadPriorityForTest::kDisplay);
+#if BUILDFLAG(IS_WIN)
+ TestPriorityResultingFromThreadType(ThreadType::kCompositing,
+ MessagePumpType::UI,
+ ThreadPriorityForTest::kNormal);
+#else
+ TestPriorityResultingFromThreadType(ThreadType::kCompositing,
+ MessagePumpType::UI,
+ ThreadPriorityForTest::kDisplay);
+#endif // BUILDFLAG(IS_WIN)
+ TestPriorityResultingFromThreadType(ThreadType::kCompositing,
+ MessagePumpType::IO,
+ ThreadPriorityForTest::kDisplay);
+#else // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_FUCHSIA)
+ TestPriorityResultingFromThreadType(ThreadType::kCompositing,
+ ThreadPriorityForTest::kNormal);
+ TestPriorityResultingFromThreadType(ThreadType::kCompositing,
+ MessagePumpType::UI,
+ ThreadPriorityForTest::kNormal);
+ TestPriorityResultingFromThreadType(ThreadType::kCompositing,
+ MessagePumpType::IO,
+ ThreadPriorityForTest::kNormal);
+#endif
+ TestPriorityResultingFromThreadType(ThreadType::kDisplayCritical,
+ ThreadPriorityForTest::kDisplay);
+ TestPriorityResultingFromThreadType(ThreadType::kRealtimeAudio,
+ ThreadPriorityForTest::kRealtimeAudio);
}
-#endif // defined(OS_WIN)
-#endif // !defined(STARBOARD)
-
-// This tests internal PlatformThread APIs used under some POSIX platforms,
-// with the exception of Mac OS X, iOS and Fuchsia.
-#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) && \
- !defined(OS_FUCHSIA)
-TEST(PlatformThreadTest, GetNiceValueToThreadPriority) {
- using internal::NiceValueToThreadPriority;
- using internal::kThreadPriorityToNiceValueMap;
-
- EXPECT_EQ(ThreadPriority::BACKGROUND,
- kThreadPriorityToNiceValueMap[0].priority);
- EXPECT_EQ(ThreadPriority::NORMAL,
- kThreadPriorityToNiceValueMap[1].priority);
- EXPECT_EQ(ThreadPriority::DISPLAY,
- kThreadPriorityToNiceValueMap[2].priority);
- EXPECT_EQ(ThreadPriority::REALTIME_AUDIO,
- kThreadPriorityToNiceValueMap[3].priority);
-
- static const int kBackgroundNiceValue =
- kThreadPriorityToNiceValueMap[0].nice_value;
- static const int kNormalNiceValue =
- kThreadPriorityToNiceValueMap[1].nice_value;
- static const int kDisplayNiceValue =
- kThreadPriorityToNiceValueMap[2].nice_value;
- static const int kRealtimeAudioNiceValue =
- kThreadPriorityToNiceValueMap[3].nice_value;
-
- // The tests below assume the nice values specified in the map are within
- // the range below (both ends exclusive).
- static const int kHighestNiceValue = 19;
- static const int kLowestNiceValue = -20;
-
- EXPECT_GT(kHighestNiceValue, kBackgroundNiceValue);
- EXPECT_GT(kBackgroundNiceValue, kNormalNiceValue);
- EXPECT_GT(kNormalNiceValue, kDisplayNiceValue);
- EXPECT_GT(kDisplayNiceValue, kRealtimeAudioNiceValue);
- EXPECT_GT(kRealtimeAudioNiceValue, kLowestNiceValue);
-
- EXPECT_EQ(ThreadPriority::BACKGROUND,
- NiceValueToThreadPriority(kHighestNiceValue));
- EXPECT_EQ(ThreadPriority::BACKGROUND,
- NiceValueToThreadPriority(kBackgroundNiceValue + 1));
- EXPECT_EQ(ThreadPriority::BACKGROUND,
- NiceValueToThreadPriority(kBackgroundNiceValue));
- EXPECT_EQ(ThreadPriority::BACKGROUND,
- NiceValueToThreadPriority(kNormalNiceValue + 1));
- EXPECT_EQ(ThreadPriority::NORMAL,
- NiceValueToThreadPriority(kNormalNiceValue));
- EXPECT_EQ(ThreadPriority::NORMAL,
- NiceValueToThreadPriority(kDisplayNiceValue + 1));
- EXPECT_EQ(ThreadPriority::DISPLAY,
- NiceValueToThreadPriority(kDisplayNiceValue));
- EXPECT_EQ(ThreadPriority::DISPLAY,
- NiceValueToThreadPriority(kRealtimeAudioNiceValue + 1));
- EXPECT_EQ(ThreadPriority::REALTIME_AUDIO,
- NiceValueToThreadPriority(kRealtimeAudioNiceValue));
- EXPECT_EQ(ThreadPriority::REALTIME_AUDIO,
- NiceValueToThreadPriority(kLowestNiceValue));
-}
-#endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) &&
- // !defined(OS_FUCHSIA)
TEST(PlatformThreadTest, SetHugeThreadName) {
// Construct an excessively long thread name.
@@ -368,4 +540,245 @@
PlatformThread::SetName(long_name);
}
+// TODO: b/327008491 - Not used by Cobalt, but should be tested to get
+// closer to Chrome.
+#if !defined(STARBOARD)
+TEST(PlatformThreadTest, GetDefaultThreadStackSize) {
+ size_t stack_size = PlatformThread::GetDefaultThreadStackSize();
+#if BUILDFLAG(IS_IOS) && BUILDFLAG(USE_BLINK)
+ EXPECT_EQ(1024u * 1024u, stack_size);
+#elif BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS) || BUILDFLAG(IS_FUCHSIA) || \
+ ((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(__GLIBC__) && \
+ !defined(THREAD_SANITIZER)) || \
+ (BUILDFLAG(IS_ANDROID) && !defined(ADDRESS_SANITIZER))
+ EXPECT_EQ(0u, stack_size);
+#else
+ EXPECT_GT(stack_size, 0u);
+ EXPECT_LT(stack_size, 20u * (1 << 20));
+#endif
+}
+#endif
+
+#if BUILDFLAG(IS_APPLE)
+
+namespace {
+
+class RealtimeTestThread : public FunctionTestThread {
+ public:
+ explicit RealtimeTestThread(TimeDelta realtime_period)
+ : realtime_period_(realtime_period) {}
+ ~RealtimeTestThread() override = default;
+
+ private:
+ RealtimeTestThread(const RealtimeTestThread&) = delete;
+ RealtimeTestThread& operator=(const RealtimeTestThread&) = delete;
+
+ TimeDelta GetRealtimePeriod() final { return realtime_period_; }
+
+ // Verifies the realtime thead configuration.
+ void RunTest() override {
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(),
+ ThreadType::kRealtimeAudio);
+
+ mach_port_t mach_thread_id = pthread_mach_thread_np(
+ PlatformThread::CurrentHandle().platform_handle());
+
+ // |count| and |get_default| chosen impirically so that
+ // time_constraints_buffer[0] would store the last constraints that were
+ // applied.
+ const int kPolicyCount = 32;
+ thread_time_constraint_policy_data_t time_constraints_buffer[kPolicyCount];
+ mach_msg_type_number_t count = kPolicyCount;
+ boolean_t get_default = 0;
+
+ kern_return_t result = thread_policy_get(
+ mach_thread_id, THREAD_TIME_CONSTRAINT_POLICY,
+ reinterpret_cast<thread_policy_t>(time_constraints_buffer), &count,
+ &get_default);
+
+ EXPECT_EQ(result, KERN_SUCCESS);
+
+ const thread_time_constraint_policy_data_t& time_constraints =
+ time_constraints_buffer[0];
+
+ mach_timebase_info_data_t tb_info;
+ mach_timebase_info(&tb_info);
+
+ if (FeatureList::IsEnabled(kOptimizedRealtimeThreadingMac) &&
+#if BUILDFLAG(IS_MAC)
+ !mac::IsOS10_14() && // Should not be applied on 10.14.
+#endif
+ !realtime_period_.is_zero()) {
+ uint32_t abs_realtime_period = saturated_cast<uint32_t>(
+ realtime_period_.InNanoseconds() *
+ (static_cast<double>(tb_info.denom) / tb_info.numer));
+
+ EXPECT_EQ(time_constraints.period, abs_realtime_period);
+ EXPECT_EQ(
+ time_constraints.computation,
+ static_cast<uint32_t>(abs_realtime_period *
+ kOptimizedRealtimeThreadingMacBusy.Get()));
+ EXPECT_EQ(
+ time_constraints.constraint,
+ static_cast<uint32_t>(abs_realtime_period *
+ kOptimizedRealtimeThreadingMacBusyLimit.Get()));
+ EXPECT_EQ(time_constraints.preemptible,
+ kOptimizedRealtimeThreadingMacPreemptible.Get());
+ } else {
+ // Old-style empirical values.
+ const double kTimeQuantum = 2.9;
+ const double kAudioTimeNeeded = 0.75 * kTimeQuantum;
+ const double kMaxTimeAllowed = 0.85 * kTimeQuantum;
+
+ // Get the conversion factor from milliseconds to absolute time
+ // which is what the time-constraints returns.
+ double ms_to_abs_time = double(tb_info.denom) / tb_info.numer * 1000000;
+
+ EXPECT_EQ(time_constraints.period,
+ saturated_cast<uint32_t>(kTimeQuantum * ms_to_abs_time));
+ EXPECT_EQ(time_constraints.computation,
+ saturated_cast<uint32_t>(kAudioTimeNeeded * ms_to_abs_time));
+ EXPECT_EQ(time_constraints.constraint,
+ saturated_cast<uint32_t>(kMaxTimeAllowed * ms_to_abs_time));
+ EXPECT_FALSE(time_constraints.preemptible);
+ }
+ }
+
+ const TimeDelta realtime_period_;
+};
+
+class RealtimePlatformThreadTest
+ : public testing::TestWithParam<
+ std::tuple<bool, FieldTrialParams, TimeDelta>> {
+ protected:
+ void VerifyRealtimeConfig(TimeDelta period) {
+ RealtimeTestThread thread(period);
+ PlatformThreadHandle handle;
+
+ ASSERT_FALSE(thread.IsRunning());
+ ASSERT_TRUE(PlatformThread::CreateWithType(0, &thread, &handle,
+ ThreadType::kRealtimeAudio));
+ thread.WaitForTerminationReady();
+ ASSERT_TRUE(thread.IsRunning());
+
+ thread.MarkForTermination();
+ PlatformThread::Join(handle);
+ ASSERT_FALSE(thread.IsRunning());
+ }
+};
+
+TEST_P(RealtimePlatformThreadTest, RealtimeAudioConfigMac) {
+ test::ScopedFeatureList feature_list;
+ if (std::get<0>(GetParam())) {
+ feature_list.InitAndEnableFeatureWithParameters(
+ kOptimizedRealtimeThreadingMac, std::get<1>(GetParam()));
+ } else {
+ feature_list.InitAndDisableFeature(kOptimizedRealtimeThreadingMac);
+ }
+
+ PlatformThread::InitFeaturesPostFieldTrial();
+ VerifyRealtimeConfig(std::get<2>(GetParam()));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ RealtimePlatformThreadTest,
+ RealtimePlatformThreadTest,
+ testing::Combine(
+ testing::Bool(),
+ testing::Values(
+ FieldTrialParams{
+ {kOptimizedRealtimeThreadingMacPreemptible.name, "true"}},
+ FieldTrialParams{
+ {kOptimizedRealtimeThreadingMacPreemptible.name, "false"}},
+ FieldTrialParams{
+ {kOptimizedRealtimeThreadingMacBusy.name, "0.5"},
+ {kOptimizedRealtimeThreadingMacBusyLimit.name, "0.75"}},
+ FieldTrialParams{
+ {kOptimizedRealtimeThreadingMacBusy.name, "0.7"},
+ {kOptimizedRealtimeThreadingMacBusyLimit.name, "0.7"}},
+ FieldTrialParams{
+ {kOptimizedRealtimeThreadingMacBusy.name, "0.5"},
+ {kOptimizedRealtimeThreadingMacBusyLimit.name, "1.0"}}),
+ testing::Values(TimeDelta(),
+ Seconds(256.0 / 48000),
+ Milliseconds(5),
+ Milliseconds(10),
+ Seconds(1024.0 / 44100),
+ Seconds(1024.0 / 16000))));
+
+} // namespace
+
+#endif // BUILDFLAG(IS_APPLE)
+
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
+namespace {
+
+bool IsTidCacheCorrect() {
+ return PlatformThread::CurrentId() == syscall(__NR_gettid);
+}
+
+void* CheckTidCacheCorrectWrapper(void*) {
+ CHECK(IsTidCacheCorrect());
+ return nullptr;
+}
+
+void CreatePthreadToCheckCache() {
+ pthread_t thread_id;
+ pthread_create(&thread_id, nullptr, CheckTidCacheCorrectWrapper, nullptr);
+ pthread_join(thread_id, nullptr);
+}
+
+// This test must use raw pthreads and fork() to avoid calls from //base to
+// PlatformThread::CurrentId(), as the ordering of calls is important to the
+// test.
+void TestTidCacheCorrect(bool main_thread_accesses_cache_first) {
+ EXPECT_TRUE(IsTidCacheCorrect());
+
+ CreatePthreadToCheckCache();
+
+ // Now fork a process and make sure the TID cache gets correctly updated on
+ // both its main thread and a child thread.
+ pid_t child_pid = fork();
+ ASSERT_GE(child_pid, 0);
+
+ if (child_pid == 0) {
+ // In the child.
+ if (main_thread_accesses_cache_first) {
+ if (!IsTidCacheCorrect())
+ _exit(1);
+ }
+
+ // Access the TID cache on another thread and make sure the cached value is
+ // correct.
+ CreatePthreadToCheckCache();
+
+ if (!main_thread_accesses_cache_first) {
+ // Make sure the main thread's cache is correct even though another thread
+ // accessed the cache first.
+ if (!IsTidCacheCorrect())
+ _exit(1);
+ }
+
+ _exit(0);
+ }
+
+ int status;
+ ASSERT_EQ(waitpid(child_pid, &status, 0), child_pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+}
+
+TEST(PlatformThreadTidCacheTest, MainThreadFirst) {
+ TestTidCacheCorrect(true);
+}
+
+TEST(PlatformThreadTidCacheTest, MainThreadSecond) {
+ TestTidCacheCorrect(false);
+}
+
+} // namespace
+
+#endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+
} // namespace base
diff --git a/base/threading/platform_thread_win.cc b/base/threading/platform_thread_win.cc
index d97c1bf..e41758d 100644
--- a/base/threading/platform_thread_win.cc
+++ b/base/threading/platform_thread_win.cc
@@ -1,35 +1,62 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/platform_thread_win.h"
-#include "base/debug/activity_tracker.h"
+#include <stddef.h>
+
+#include <string>
+
+#include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
#include "base/debug/alias.h"
+#include "base/debug/crash_logging.h"
#include "base/debug/profiler.h"
+#include "base/feature_list.h"
#include "base/logging.h"
+#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
+#include "base/process/memory.h"
+#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/threading/scoped_thread_priority.h"
#include "base/threading/thread_id_name_manager.h"
#include "base/threading/thread_restrictions.h"
+#include "base/threading/threading_features.h"
+#include "base/time/time_override.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
+#include "build/build_config.h"
#include <windows.h>
-#include "starboard/types.h"
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
+#include "base/allocator/partition_allocator/starscan/pcscan.h"
+#include "base/allocator/partition_allocator/starscan/stack/stack.h"
+#endif
namespace base {
+BASE_FEATURE(kUseThreadPriorityLowest,
+ "UseThreadPriorityLowest",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kAboveNormalCompositingBrowserWin,
+ "AboveNormalCompositingBrowserWin",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+
namespace {
-// The value returned by ::GetThreadPriority() after background thread mode is
-// enabled on Windows 7.
-constexpr int kWin7BackgroundThreadModePriority = 4;
+// Flag used to set thread priority to |THREAD_PRIORITY_LOWEST| for
+// |kUseThreadPriorityLowest| Feature.
+std::atomic<bool> g_use_thread_priority_lowest{false};
+// Flag used to map Compositing ThreadType |THREAD_PRIORITY_ABOVE_NORMAL| on the
+// UI thread for |kAboveNormalCompositingBrowserWin| Feature.
+std::atomic<bool> g_above_normal_compositing_browser{false};
-// The value returned by ::GetThreadPriority() after background thread mode is
-// enabled on Windows 8+.
-constexpr int kWin8AboveBackgroundThreadModePriority = -4;
+// These values are sometimes returned by ::GetThreadPriority().
+constexpr int kWinDisplayPriority1 = 5;
+constexpr int kWinDisplayPriority2 = 6;
// The information on how to set the thread name comes from
// a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx
@@ -55,26 +82,28 @@
info.dwFlags = 0;
__try {
- RaiseException(kVCThreadNameException, 0, sizeof(info)/sizeof(DWORD),
- reinterpret_cast<DWORD_PTR*>(&info));
- } __except(EXCEPTION_CONTINUE_EXECUTION) {
+ RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(ULONG_PTR),
+ reinterpret_cast<ULONG_PTR*>(&info));
+ } __except (EXCEPTION_EXECUTE_HANDLER) {
}
}
struct ThreadParams {
- PlatformThread::Delegate* delegate;
+ raw_ptr<PlatformThread::Delegate> delegate;
bool joinable;
- ThreadPriority priority;
+ ThreadType thread_type;
+ MessagePumpType message_pump_type;
};
DWORD __stdcall ThreadFunc(void* params) {
ThreadParams* thread_params = static_cast<ThreadParams*>(params);
PlatformThread::Delegate* delegate = thread_params->delegate;
if (!thread_params->joinable)
- base::ThreadRestrictions::SetSingletonAllowed(false);
+ base::DisallowSingleton();
- if (thread_params->priority != ThreadPriority::NORMAL)
- PlatformThread::SetCurrentThreadPriority(thread_params->priority);
+ if (thread_params->thread_type != ThreadType::kDefault)
+ internal::SetCurrentThreadType(thread_params->thread_type,
+ thread_params->message_pump_type);
// Retrieve a copy of the thread handle to use as the key in the
// thread name mapping.
@@ -87,59 +116,105 @@
FALSE,
DUPLICATE_SAME_ACCESS);
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
+ partition_alloc::internal::PCScan::NotifyThreadCreated(
+ partition_alloc::internal::GetStackPointer());
+#endif
+
win::ScopedHandle scoped_platform_handle;
if (did_dup) {
scoped_platform_handle.Set(platform_handle);
ThreadIdNameManager::GetInstance()->RegisterThread(
- scoped_platform_handle.Get(),
- PlatformThread::CurrentId());
+ scoped_platform_handle.get(), PlatformThread::CurrentId());
}
delete thread_params;
delegate->ThreadMain();
if (did_dup) {
- ThreadIdNameManager::GetInstance()->RemoveName(
- scoped_platform_handle.Get(),
- PlatformThread::CurrentId());
+ ThreadIdNameManager::GetInstance()->RemoveName(scoped_platform_handle.get(),
+ PlatformThread::CurrentId());
}
+#if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && BUILDFLAG(USE_STARSCAN)
+ partition_alloc::internal::PCScan::NotifyThreadDestroyed();
+#endif
+
+ // Ensure thread priority is at least NORMAL before initiating thread
+ // destruction. Thread destruction on Windows holds the LdrLock while
+ // performing TLS destruction which causes hangs if performed at background
+ // priority (priority inversion) (see: http://crbug.com/1096203).
+ if (::GetThreadPriority(::GetCurrentThread()) < THREAD_PRIORITY_NORMAL)
+ PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
+
return 0;
}
-// CreateThreadInternal() matches PlatformThread::CreateWithPriority(), except
+// CreateThreadInternal() matches PlatformThread::CreateWithType(), except
// that |out_thread_handle| may be nullptr, in which case a non-joinable thread
// is created.
bool CreateThreadInternal(size_t stack_size,
PlatformThread::Delegate* delegate,
PlatformThreadHandle* out_thread_handle,
- ThreadPriority priority) {
+ ThreadType thread_type,
+ MessagePumpType message_pump_type) {
unsigned int flags = 0;
if (stack_size > 0) {
flags = STACK_SIZE_PARAM_IS_A_RESERVATION;
+#if defined(ARCH_CPU_32_BITS)
+ } else {
+ // The process stack size is increased to give spaces to |RendererMain| in
+ // |chrome/BUILD.gn|, but keep the default stack size of other threads to
+ // 1MB for the address space pressure.
+ flags = STACK_SIZE_PARAM_IS_A_RESERVATION;
+ static BOOL is_wow64 = -1;
+ if (is_wow64 == -1 && !IsWow64Process(GetCurrentProcess(), &is_wow64))
+ is_wow64 = FALSE;
+ // When is_wow64 is set that means we are running on 64-bit Windows and we
+ // get 4 GiB of address space. In that situation we can afford to use 1 MiB
+ // of address space for stacks. When running on 32-bit Windows we only get
+ // 2 GiB of address space so we need to conserve. Typically stack usage on
+ // these threads is only about 100 KiB.
+ if (is_wow64)
+ stack_size = 1024 * 1024;
+ else
+ stack_size = 512 * 1024;
+#endif
}
ThreadParams* params = new ThreadParams;
params->delegate = delegate;
params->joinable = out_thread_handle != nullptr;
- params->priority = priority;
+ params->thread_type = thread_type;
+ params->message_pump_type = message_pump_type;
- void* thread_handle;
- {
- SCOPED_UMA_HISTOGRAM_TIMER("Windows.CreateThreadTime");
-
- // Using CreateThread here vs _beginthreadex makes thread creation a bit
- // faster and doesn't require the loader lock to be available. Our code
- // will have to work running on CreateThread() threads anyway, since we run
- // code on the Windows thread pool, etc. For some background on the
- // difference:
- // http://www.microsoft.com/msj/1099/win32/win321099.aspx
- thread_handle =
- ::CreateThread(nullptr, stack_size, ThreadFunc, params, flags, nullptr);
- }
+ // Using CreateThread here vs _beginthreadex makes thread creation a bit
+ // faster and doesn't require the loader lock to be available. Our code will
+ // have to work running on CreateThread() threads anyway, since we run code on
+ // the Windows thread pool, etc. For some background on the difference:
+ // http://www.microsoft.com/msj/1099/win32/win321099.aspx
+ void* thread_handle =
+ ::CreateThread(nullptr, stack_size, ThreadFunc, params, flags, nullptr);
if (!thread_handle) {
+ DWORD last_error = ::GetLastError();
+
+ switch (last_error) {
+ case ERROR_NOT_ENOUGH_MEMORY:
+ case ERROR_OUTOFMEMORY:
+ case ERROR_COMMITMENT_LIMIT:
+ TerminateBecauseOutOfMemory(stack_size);
+ break;
+
+ default:
+ static auto* last_error_crash_key = debug::AllocateCrashKeyString(
+ "create_thread_last_error", debug::CrashKeySize::Size32);
+ debug::SetCrashKeyString(last_error_crash_key,
+ base::NumberToString(last_error));
+ break;
+ }
+
delete params;
return false;
}
@@ -153,10 +228,27 @@
} // namespace
-namespace features {
-const Feature kWindowsThreadModeBackground{"WindowsThreadModeBackground",
- FEATURE_DISABLED_BY_DEFAULT};
-} // namespace features
+namespace internal {
+
+void AssertMemoryPriority(HANDLE thread, int memory_priority) {
+#if DCHECK_IS_ON()
+ static const auto get_thread_information_fn =
+ reinterpret_cast<decltype(&::GetThreadInformation)>(::GetProcAddress(
+ ::GetModuleHandle(L"Kernel32.dll"), "GetThreadInformation"));
+
+ DCHECK(get_thread_information_fn);
+
+ MEMORY_PRIORITY_INFORMATION memory_priority_information = {};
+ DCHECK(get_thread_information_fn(thread, ::ThreadMemoryPriority,
+ &memory_priority_information,
+ sizeof(memory_priority_information)));
+
+ DCHECK_EQ(memory_priority,
+ static_cast<int>(memory_priority_information.MemoryPriority));
+#endif
+}
+
+} // namespace internal
// static
PlatformThreadId PlatformThread::CurrentId() {
@@ -182,9 +274,13 @@
void PlatformThread::Sleep(TimeDelta duration) {
// When measured with a high resolution clock, Sleep() sometimes returns much
// too early. We may need to call it repeatedly to get the desired duration.
- TimeTicks end = TimeTicks::Now() + duration;
- for (TimeTicks now = TimeTicks::Now(); now < end; now = TimeTicks::Now())
+ // PlatformThread::Sleep doesn't support mock-time, so this always uses
+ // real-time.
+ const TimeTicks end = subtle::TimeTicksNowIgnoringOverride() + duration;
+ for (TimeTicks now = subtle::TimeTicksNowIgnoringOverride(); now < end;
+ now = subtle::TimeTicksNowIgnoringOverride()) {
::Sleep(static_cast<DWORD>((end - now).InMillisecondsRoundedUp()));
+ }
}
// static
@@ -192,7 +288,7 @@
ThreadIdNameManager::GetInstance()->SetName(name);
// The SetThreadDescription API works even if no debugger is attached.
- auto set_thread_description_func =
+ static auto set_thread_description_func =
reinterpret_cast<SetThreadDescription>(::GetProcAddress(
::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription"));
if (set_thread_description_func) {
@@ -214,36 +310,33 @@
}
// static
-bool PlatformThread::CreateWithPriority(size_t stack_size, Delegate* delegate,
- PlatformThreadHandle* thread_handle,
- ThreadPriority priority) {
+bool PlatformThread::CreateWithType(size_t stack_size,
+ Delegate* delegate,
+ PlatformThreadHandle* thread_handle,
+ ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
DCHECK(thread_handle);
- return CreateThreadInternal(stack_size, delegate, thread_handle, priority);
+ return CreateThreadInternal(stack_size, delegate, thread_handle, thread_type,
+ pump_type_hint);
}
// static
bool PlatformThread::CreateNonJoinable(size_t stack_size, Delegate* delegate) {
- return CreateNonJoinableWithPriority(stack_size, delegate,
- ThreadPriority::NORMAL);
+ return CreateNonJoinableWithType(stack_size, delegate, ThreadType::kDefault);
}
// static
-bool PlatformThread::CreateNonJoinableWithPriority(size_t stack_size,
- Delegate* delegate,
- ThreadPriority priority) {
+bool PlatformThread::CreateNonJoinableWithType(size_t stack_size,
+ Delegate* delegate,
+ ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
return CreateThreadInternal(stack_size, delegate, nullptr /* non-joinable */,
- priority);
+ thread_type, pump_type_hint);
}
// static
void PlatformThread::Join(PlatformThreadHandle thread_handle) {
DCHECK(thread_handle.platform_handle());
- // TODO(willchan): Enable this check once I can get it to work for Windows
- // shutdown.
- // Joining another thread may block the current thread for a long time, since
- // the thread referred to by |thread_handle| may still be running long-lived /
- // blocking tasks.
- // AssertBlockingAllowed();
DWORD thread_id = 0;
thread_id = ::GetThreadId(thread_handle.platform_handle());
@@ -255,8 +348,8 @@
base::debug::Alias(&thread_id);
base::debug::Alias(&last_error);
- // Record the event that this thread is blocking upon (for hang diagnosis).
- base::debug::ScopedThreadJoinActivity thread_activity(&thread_handle);
+ base::internal::ScopedBlockingCallWithBaseSyncPrimitives scoped_blocking_call(
+ FROM_HERE, base::BlockingType::MAY_BLOCK);
// Wait for the thread to exit. It should already have terminated but make
// sure this assumption is valid.
@@ -271,92 +364,211 @@
}
// static
-bool PlatformThread::CanIncreaseThreadPriority(ThreadPriority priority) {
+bool PlatformThread::CanChangeThreadType(ThreadType from, ThreadType to) {
return true;
}
-// static
-void PlatformThread::SetCurrentThreadPriority(ThreadPriority priority) {
- // A DCHECK is triggered on FeatureList initialization if the state of a
- // feature has been checked before. We only want to trigger that DCHECK if the
- // priority has been set to BACKGROUND before, so we are careful not to access
- // the state of the feature needlessly. We don't DCHECK here because it is ok
- // if the FeatureList is never initialized in the process (e.g. in tests).
- //
- // TODO(fdoray): Remove experiment code. https://crbug.com/872820
- const bool use_thread_mode_background =
- (priority == ThreadPriority::BACKGROUND
- ? FeatureList::IsEnabled(features::kWindowsThreadModeBackground)
- : (FeatureList::GetInstance() &&
- FeatureList::IsEnabled(features::kWindowsThreadModeBackground)));
+namespace {
- if (use_thread_mode_background && priority != ThreadPriority::BACKGROUND) {
+void SetCurrentThreadPriority(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
+ if (thread_type == ThreadType::kCompositing &&
+ pump_type_hint == MessagePumpType::UI &&
+ !g_above_normal_compositing_browser) {
+ // Ignore kCompositing thread type for UI thread as Windows has a
+ // priority boost mechanism. See
+ // https://docs.microsoft.com/en-us/windows/win32/procthread/priority-boosts
+ return;
+ }
+
+ PlatformThreadHandle::Handle thread_handle =
+ PlatformThread::CurrentHandle().platform_handle();
+
+ if (!g_use_thread_priority_lowest && thread_type != ThreadType::kBackground) {
// Exit background mode if the new priority is not BACKGROUND. This is a
// no-op if not in background mode.
- ::SetThreadPriority(PlatformThread::CurrentHandle().platform_handle(),
- THREAD_MODE_BACKGROUND_END);
+ ::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_END);
+ // We used to DCHECK that memory priority is MEMORY_PRIORITY_NORMAL here,
+ // but found that it is not always the case (e.g. in the installer).
+ // crbug.com/1340578#c2
}
int desired_priority = THREAD_PRIORITY_ERROR_RETURN;
- switch (priority) {
- case ThreadPriority::BACKGROUND:
- desired_priority = use_thread_mode_background
- ? THREAD_MODE_BACKGROUND_BEGIN
- : THREAD_PRIORITY_LOWEST;
+ switch (thread_type) {
+ case ThreadType::kBackground:
+ // Using THREAD_MODE_BACKGROUND_BEGIN instead of THREAD_PRIORITY_LOWEST
+ // improves input latency and navigation time. See
+ // https://docs.google.com/document/d/16XrOwuwTwKWdgPbcKKajTmNqtB4Am8TgS9GjbzBYLc0
+ //
+ // MSDN recommends THREAD_MODE_BACKGROUND_BEGIN for threads that perform
+ // background work, as it reduces disk and memory priority in addition to
+ // CPU priority.
+ desired_priority =
+ g_use_thread_priority_lowest.load(std::memory_order_relaxed)
+ ? THREAD_PRIORITY_LOWEST
+ : THREAD_MODE_BACKGROUND_BEGIN;
break;
- case ThreadPriority::NORMAL:
+ case ThreadType::kUtility:
+ desired_priority = THREAD_PRIORITY_BELOW_NORMAL;
+ break;
+ case ThreadType::kResourceEfficient:
+ case ThreadType::kDefault:
desired_priority = THREAD_PRIORITY_NORMAL;
break;
- case ThreadPriority::DISPLAY:
+ case ThreadType::kCompositing:
+ case ThreadType::kDisplayCritical:
desired_priority = THREAD_PRIORITY_ABOVE_NORMAL;
break;
- case ThreadPriority::REALTIME_AUDIO:
+ case ThreadType::kRealtimeAudio:
desired_priority = THREAD_PRIORITY_TIME_CRITICAL;
break;
- default:
- NOTREACHED() << "Unknown priority.";
- break;
}
DCHECK_NE(desired_priority, THREAD_PRIORITY_ERROR_RETURN);
-#if DCHECK_IS_ON()
- const BOOL success =
-#endif
- ::SetThreadPriority(PlatformThread::CurrentHandle().platform_handle(),
- desired_priority);
- DPLOG_IF(ERROR, !success) << "Failed to set thread priority to "
- << desired_priority;
+ [[maybe_unused]] const BOOL success =
+ ::SetThreadPriority(thread_handle, desired_priority);
+ DPLOG_IF(ERROR, !success)
+ << "Failed to set thread priority to " << desired_priority;
- // Sanity check that GetCurrentThreadPriority() is consistent with
- // SetCurrentThreadPriority().
- DCHECK_EQ(GetCurrentThreadPriority(), priority);
+ if (!g_use_thread_priority_lowest && thread_type == ThreadType::kBackground) {
+ // In a background process, THREAD_MODE_BACKGROUND_BEGIN lowers the memory
+ // and I/O priorities but not the CPU priority (kernel bug?). Use
+ // THREAD_PRIORITY_LOWEST to also lower the CPU priority.
+ // https://crbug.com/901483
+ if (PlatformThread::GetCurrentThreadPriorityForTest() !=
+ ThreadPriorityForTest::kBackground) {
+ ::SetThreadPriority(thread_handle, THREAD_PRIORITY_LOWEST);
+ // We used to DCHECK that memory priority is MEMORY_PRIORITY_VERY_LOW
+ // here, but found that it is not always the case (e.g. in the installer).
+ // crbug.com/1340578#c2
+ }
+ }
}
+void SetCurrentThreadQualityOfService(ThreadType thread_type) {
+ // QoS and power throttling were introduced in Win10 1709
+ if (win::GetVersion() < win::Version::WIN10_RS3) {
+ return;
+ }
+
+ static const auto set_thread_information_fn =
+ reinterpret_cast<decltype(&::SetThreadInformation)>(::GetProcAddress(
+ ::GetModuleHandle(L"kernel32.dll"), "SetThreadInformation"));
+ DCHECK(set_thread_information_fn);
+
+ bool desire_ecoqos = false;
+ switch (thread_type) {
+ case ThreadType::kBackground:
+ case ThreadType::kUtility:
+ case ThreadType::kResourceEfficient:
+ desire_ecoqos = true;
+ break;
+ case ThreadType::kDefault:
+ case ThreadType::kCompositing:
+ case ThreadType::kDisplayCritical:
+ case ThreadType::kRealtimeAudio:
+ desire_ecoqos = false;
+ break;
+ }
+
+ THREAD_POWER_THROTTLING_STATE thread_power_throttling_state{
+ .Version = THREAD_POWER_THROTTLING_CURRENT_VERSION,
+ .ControlMask =
+ desire_ecoqos ? THREAD_POWER_THROTTLING_EXECUTION_SPEED : 0ul,
+ .StateMask =
+ desire_ecoqos ? THREAD_POWER_THROTTLING_EXECUTION_SPEED : 0ul,
+ };
+ [[maybe_unused]] const BOOL success = set_thread_information_fn(
+ ::GetCurrentThread(), ::ThreadPowerThrottling,
+ &thread_power_throttling_state, sizeof(thread_power_throttling_state));
+ DPLOG_IF(ERROR, !success)
+ << "Failed to set EcoQoS to " << std::boolalpha << desire_ecoqos;
+}
+
+} // namespace
+
+namespace internal {
+
+void SetCurrentThreadTypeImpl(ThreadType thread_type,
+ MessagePumpType pump_type_hint) {
+ SetCurrentThreadPriority(thread_type, pump_type_hint);
+ SetCurrentThreadQualityOfService(thread_type);
+}
+
+} // namespace internal
+
// static
-ThreadPriority PlatformThread::GetCurrentThreadPriority() {
+ThreadPriorityForTest PlatformThread::GetCurrentThreadPriorityForTest() {
+ static_assert(
+ THREAD_PRIORITY_IDLE < 0,
+ "THREAD_PRIORITY_IDLE is >= 0 and will incorrectly cause errors.");
+ static_assert(
+ THREAD_PRIORITY_LOWEST < 0,
+ "THREAD_PRIORITY_LOWEST is >= 0 and will incorrectly cause errors.");
+ static_assert(THREAD_PRIORITY_BELOW_NORMAL < 0,
+ "THREAD_PRIORITY_BELOW_NORMAL is >= 0 and will incorrectly "
+ "cause errors.");
+ static_assert(
+ THREAD_PRIORITY_NORMAL == 0,
+ "The logic below assumes that THREAD_PRIORITY_NORMAL is zero. If it is "
+ "not, ThreadPriorityForTest::kBackground may be incorrectly detected.");
+ static_assert(THREAD_PRIORITY_ABOVE_NORMAL >= 0,
+ "THREAD_PRIORITY_ABOVE_NORMAL is < 0 and will incorrectly be "
+ "translated to ThreadPriorityForTest::kBackground.");
+ static_assert(THREAD_PRIORITY_HIGHEST >= 0,
+ "THREAD_PRIORITY_HIGHEST is < 0 and will incorrectly be "
+ "translated to ThreadPriorityForTest::kBackground.");
+ static_assert(THREAD_PRIORITY_TIME_CRITICAL >= 0,
+ "THREAD_PRIORITY_TIME_CRITICAL is < 0 and will incorrectly be "
+ "translated to ThreadPriorityForTest::kBackground.");
+ static_assert(THREAD_PRIORITY_ERROR_RETURN >= 0,
+ "THREAD_PRIORITY_ERROR_RETURN is < 0 and will incorrectly be "
+ "translated to ThreadPriorityForTest::kBackground.");
+
const int priority =
::GetThreadPriority(PlatformThread::CurrentHandle().platform_handle());
+ // Negative values represent a background priority. We have observed -3, -4,
+ // -6 when THREAD_MODE_BACKGROUND_* is used. THREAD_PRIORITY_IDLE,
+ // THREAD_PRIORITY_LOWEST and THREAD_PRIORITY_BELOW_NORMAL are other possible
+ // negative values.
+ if (priority < THREAD_PRIORITY_BELOW_NORMAL)
+ return ThreadPriorityForTest::kBackground;
+
switch (priority) {
- case THREAD_PRIORITY_IDLE:
- case kWin7BackgroundThreadModePriority:
- DCHECK_EQ(win::GetVersion(), win::VERSION_WIN7);
- FALLTHROUGH;
- case kWin8AboveBackgroundThreadModePriority:
- case THREAD_PRIORITY_LOWEST:
- return ThreadPriority::BACKGROUND;
+ case THREAD_PRIORITY_BELOW_NORMAL:
+ return ThreadPriorityForTest::kUtility;
case THREAD_PRIORITY_NORMAL:
- return ThreadPriority::NORMAL;
+ return ThreadPriorityForTest::kNormal;
+ case kWinDisplayPriority1:
+ [[fallthrough]];
+ case kWinDisplayPriority2:
+ return ThreadPriorityForTest::kDisplay;
case THREAD_PRIORITY_ABOVE_NORMAL:
- return ThreadPriority::DISPLAY;
+ case THREAD_PRIORITY_HIGHEST:
+ return ThreadPriorityForTest::kDisplay;
case THREAD_PRIORITY_TIME_CRITICAL:
- return ThreadPriority::REALTIME_AUDIO;
+ return ThreadPriorityForTest::kRealtimeAudio;
case THREAD_PRIORITY_ERROR_RETURN:
- DPCHECK(false) << "GetThreadPriority error";
+ DPCHECK(false) << "::GetThreadPriority error";
}
- NOTREACHED() << "GetCurrentThreadPriority returned " << priority << ".";
- return ThreadPriority::NORMAL;
+ NOTREACHED() << "::GetThreadPriority returned " << priority << ".";
+ return ThreadPriorityForTest::kNormal;
+}
+
+void InitializePlatformThreadFeatures() {
+ g_use_thread_priority_lowest.store(
+ FeatureList::IsEnabled(kUseThreadPriorityLowest),
+ std::memory_order_relaxed);
+ g_above_normal_compositing_browser.store(
+ FeatureList::IsEnabled(kAboveNormalCompositingBrowserWin),
+ std::memory_order_relaxed);
+}
+
+// static
+size_t PlatformThread::GetDefaultThreadStackSize() {
+ return 0;
}
} // namespace base
diff --git a/base/threading/platform_thread_win.h b/base/threading/platform_thread_win.h
index 61cfd00..18a7260 100644
--- a/base/threading/platform_thread_win.h
+++ b/base/threading/platform_thread_win.h
@@ -1,26 +1,27 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_PLATFORM_THREAD_WIN_H_
#define BASE_THREADING_PLATFORM_THREAD_WIN_H_
+#include "base/win/windows_types.h"
+
#include "base/threading/platform_thread.h"
#include "base/base_export.h"
-#include "base/feature_list.h"
namespace base {
-namespace features {
+namespace internal {
-// Use THREAD_MODE_BACKGROUND_BEGIN instead of THREAD_PRIORITY_LOWEST for
-// ThreadPriority::BACKGROUND threads. This lowers the disk and network I/O
-// priority of the thread in addition to the CPU scheduling priority. MSDN
-// recommends using this setting for threads that perform background work.
-// https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-setthreadpriority
-BASE_EXPORT extern const Feature kWindowsThreadModeBackground;
+// Assert that the memory priority of `thread` is `memory_priority`. Exposed
+// for unit tests.
+BASE_EXPORT void AssertMemoryPriority(HANDLE thread, int memory_priority);
-} // namespace features
+} // namespace internal
+
+BASE_EXPORT void InitializePlatformThreadFeatures();
+
} // namespace base
#endif // BASE_THREADING_PLATFORM_THREAD_WIN_H_
diff --git a/base/threading/platform_thread_win_unittest.cc b/base/threading/platform_thread_win_unittest.cc
new file mode 100644
index 0000000..3fc3a9e
--- /dev/null
+++ b/base/threading/platform_thread_win_unittest.cc
@@ -0,0 +1,91 @@
+// Copyright 2018 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/platform_thread_win.h"
+
+#include <windows.h>
+
+#include <array>
+
+#include "base/process/process.h"
+#include "base/win/windows_version.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// It has been observed that calling
+// :SetThreadPriority(THREAD_MODE_BACKGROUND_BEGIN) in an IDLE_PRIORITY_CLASS
+// process never affects the return value of ::GetThreadPriority() or
+// the base priority reported in Process Explorer. It does however
+// set the memory and I/O priorities to very low. This test confirms that
+// behavior which we suspect is a Windows kernel bug. If this test starts
+// failing, the mitigation for https://crbug.com/901483 in
+// PlatformThread::SetCurrentThreadType() should be revisited.
+TEST(PlatformThreadWinTest, SetBackgroundThreadModeFailsInIdlePriorityProcess) {
+ PlatformThreadHandle::Handle thread_handle =
+ PlatformThread::CurrentHandle().platform_handle();
+
+ // ::GetThreadPriority() is NORMAL. Memory priority is NORMAL.
+ // Note: There is no practical way to verify the I/O priority.
+ EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL);
+ internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL);
+
+ // Set the process priority to IDLE.
+ // Note: Do not use Process::SetProcessBackgrounded() because it uses
+ // PROCESS_MODE_BACKGROUND_BEGIN instead of IDLE_PRIORITY_CLASS when
+ // the target is the current process.
+ EXPECT_EQ(::GetPriorityClass(Process::Current().Handle()),
+ static_cast<DWORD>(NORMAL_PRIORITY_CLASS));
+ ::SetPriorityClass(Process::Current().Handle(), IDLE_PRIORITY_CLASS);
+ EXPECT_EQ(Process::Current().GetPriority(),
+ static_cast<int>(IDLE_PRIORITY_CLASS));
+
+ // GetThreadPriority() stays NORMAL. Memory priority stays NORMAL.
+ EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL);
+ internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL);
+
+ // Begin thread mode background.
+ EXPECT_TRUE(::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_BEGIN));
+
+ // On Win10+, GetThreadPriority() stays NORMAL and memory priority becomes
+ // VERY_LOW.
+ //
+ // Note: this documents the aforementioned kernel bug. Ideally this would
+ // *not* be the case.
+ const int priority_after_thread_mode_background_begin =
+ ::GetThreadPriority(thread_handle);
+ EXPECT_EQ(priority_after_thread_mode_background_begin,
+ THREAD_PRIORITY_NORMAL);
+ internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW);
+
+ PlatformThread::Sleep(base::Seconds(1));
+
+ // After 1 second, GetThreadPriority() and memory priority don't change (this
+ // refutes the hypothesis that it simply takes time before GetThreadPriority()
+ // is updated after entering thread mode background).
+ EXPECT_EQ(::GetThreadPriority(thread_handle),
+ priority_after_thread_mode_background_begin);
+ internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW);
+
+ // Set the process priority to NORMAL.
+ ::SetPriorityClass(Process::Current().Handle(), NORMAL_PRIORITY_CLASS);
+
+ // GetThreadPriority() and memory priority don't change when the process
+ // priority changes.
+ EXPECT_EQ(::GetThreadPriority(thread_handle),
+ priority_after_thread_mode_background_begin);
+ internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_VERY_LOW);
+
+ // End thread mode background.
+ //
+ // Note: at least "ending" the semi-enforced background mode works...
+ EXPECT_TRUE(::SetThreadPriority(thread_handle, THREAD_MODE_BACKGROUND_END));
+
+ // GetThreadPriority() stays/becomes NORMAL. Memory priority becomes NORMAL.
+ EXPECT_EQ(::GetThreadPriority(thread_handle), THREAD_PRIORITY_NORMAL);
+ internal::AssertMemoryPriority(thread_handle, MEMORY_PRIORITY_NORMAL);
+}
+
+} // namespace base
diff --git a/base/threading/post_task_and_reply_impl.cc b/base/threading/post_task_and_reply_impl.cc
index 5aacdad..fb72de3 100644
--- a/base/threading/post_task_and_reply_impl.cc
+++ b/base/threading/post_task_and_reply_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,12 +6,12 @@
#include <utility>
-#include "base/bind.h"
+#include "base/check_op.h"
#include "base/debug/leak_annotations.h"
-#include "base/logging.h"
+#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
-#include "base/sequenced_task_runner.h"
-#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
namespace base {
@@ -21,55 +21,82 @@
public:
PostTaskAndReplyRelay(const Location& from_here,
OnceClosure task,
- OnceClosure reply)
+ OnceClosure reply,
+ scoped_refptr<SequencedTaskRunner> reply_task_runner)
: from_here_(from_here),
task_(std::move(task)),
- reply_(std::move(reply)) {}
+ reply_(std::move(reply)),
+ reply_task_runner_(std::move(reply_task_runner)) {}
PostTaskAndReplyRelay(PostTaskAndReplyRelay&&) = default;
+ PostTaskAndReplyRelay(const PostTaskAndReplyRelay&) = delete;
+ PostTaskAndReplyRelay& operator=(const PostTaskAndReplyRelay&) = delete;
+
+ // It is important that |reply_| always be deleted on the origin sequence
+ // (|reply_task_runner_|) since its destructor can be affine to it. More
+ // sutbly, it is also important that |task_| be destroyed on the origin
+ // sequence when it fails to run. This is because |task_| can own state which
+ // is affine to |reply_task_runner_| and was intended to be handed to
+ // |reply_|, e.g. https://crbug.com/829122. Since |task_| already needs to
+ // support deletion on the origin sequence (since the initial PostTask can
+ // always fail), it's safer to delete it there when PostTask succeeds but
+ // |task_| is later prevented from running.
+ //
+ // PostTaskAndReplyRelay's move semantics along with logic in this destructor
+ // enforce the above semantics in all the following cases :
+ // 1) Posting |task_| fails right away on the origin sequence:
+ // a) |reply_task_runner_| is null (i.e. during late shutdown);
+ // b) |reply_task_runner_| is set.
+ // 2) ~PostTaskAndReplyRelay() runs on the destination sequence:
+ // a) RunTaskAndPostReply() is cancelled before running;
+ // b) RunTaskAndPostReply() is skipped on shutdown;
+ // c) Posting RunReply() fails.
+ // 3) ~PostTaskAndReplyRelay() runs on the origin sequence:
+ // a) RunReply() is cancelled before running;
+ // b) RunReply() is skipped on shutdown;
+ // c) The DeleteSoon() posted by (2) runs.
+ // 4) ~PostTaskAndReplyRelay() should no-op:
+ // a) This relay was moved to another relay instance;
+ // b) RunReply() ran and completed this relay's mandate.
~PostTaskAndReplyRelay() {
- if (reply_) {
- // This can run:
- // 1) On origin sequence, when:
- // 1a) Posting |task_| fails.
- // 1b) |reply_| is cancelled before running.
- // 1c) The DeleteSoon() below is scheduled.
- // 2) On destination sequence, when:
- // 2a) |task_| is cancelled before running.
- // 2b) Posting |reply_| fails.
-
- if (!reply_task_runner_->RunsTasksInCurrentSequence()) {
- // Case 2a) or 2b).
- //
- // Destroy callbacks asynchronously on |reply_task_runner| since their
- // destructors can rightfully be affine to it. As always, DeleteSoon()
- // might leak its argument if the target execution environment is
- // shutdown (e.g. MessageLoop deleted, TaskScheduler shutdown).
- //
- // Note: while it's obvious why |reply_| can be affine to
- // |reply_task_runner|, the reason that |task_| can also be affine to it
- // is that it if neither tasks ran, |task_| may still hold an object
- // which was intended to be moved to |reply_| when |task_| ran (such an
- // object's destruction can be affine to |reply_task_runner_| -- e.g.
- // https://crbug.com/829122).
- auto relay_to_delete =
- std::make_unique<PostTaskAndReplyRelay>(std::move(*this));
- ANNOTATE_LEAKING_OBJECT_PTR(relay_to_delete.get());
- reply_task_runner_->DeleteSoon(from_here_, std::move(relay_to_delete));
- }
-
- // Case 1a), 1b), 1c).
- //
- // Callbacks will be destroyed synchronously at the end of this scope.
- } else {
- // This can run when both callbacks have run or have been moved to another
- // PostTaskAndReplyRelay instance. If |reply_| is null, |task_| must be
- // null too.
- DCHECK(!task_);
+ // Case 1a and 4a:
+ if (!reply_task_runner_) {
+ DCHECK_EQ(task_.is_null(), reply_.is_null());
+ return;
}
+
+ // Case 4b:
+ if (!reply_) {
+ DCHECK(!task_);
+ return;
+ }
+
+ // Case 2:
+ if (!reply_task_runner_->RunsTasksInCurrentSequence()) {
+ DCHECK(reply_);
+ // Allow this task to be leaked on shutdown even if `reply_task_runner_`
+ // has the TaskShutdownBehaviour::BLOCK_SHUTDOWN trait. Without `fizzler`,
+ // such a task runner would DCHECK when posting to `reply_task_runner_`
+ // after shutdown. Ignore this DCHECK as the poster isn't in control when
+ // its Callback is destroyed late into shutdown. Ref. crbug.com/1375270.
+ base::ThreadPoolInstance::ScopedFizzleBlockShutdownTasks fizzler;
+
+ SequencedTaskRunner* reply_task_runner_raw = reply_task_runner_.get();
+ auto relay_to_delete =
+ std::make_unique<PostTaskAndReplyRelay>(std::move(*this));
+ // In case 2c, posting the DeleteSoon will also fail and |relay_to_delete|
+ // will be leaked. This only happens during shutdown and leaking is better
+ // than thread-unsafe execution.
+ ANNOTATE_LEAKING_OBJECT_PTR(relay_to_delete.get());
+ reply_task_runner_raw->DeleteSoon(from_here_, std::move(relay_to_delete));
+ return;
+ }
+
+ // Case 1b and 3: Any remaining state will be destroyed synchronously at the
+ // end of this scope.
}
- // No assignment operator because of const members.
+ // No assignment operator because of const member.
PostTaskAndReplyRelay& operator=(PostTaskAndReplyRelay&&) = delete;
// Static function is used because it is not possible to bind a method call to
@@ -78,13 +105,13 @@
DCHECK(relay.task_);
std::move(relay.task_).Run();
- // Keep a reference to the reply TaskRunner for the PostTask() call before
+ // Keep a pointer to the reply TaskRunner for the PostTask() call before
// |relay| is moved into a callback.
- scoped_refptr<SequencedTaskRunner> reply_task_runner =
- relay.reply_task_runner_;
+ SequencedTaskRunner* reply_task_runner_raw = relay.reply_task_runner_.get();
- reply_task_runner->PostTask(
- relay.from_here_,
+ const Location from_here = relay.from_here_;
+ reply_task_runner_raw->PostTask(
+ from_here,
BindOnce(&PostTaskAndReplyRelay::RunReply, std::move(relay)));
}
@@ -100,10 +127,8 @@
const Location from_here_;
OnceClosure task_;
OnceClosure reply_;
- const scoped_refptr<SequencedTaskRunner> reply_task_runner_ =
- SequencedTaskRunnerHandle::Get();
-
- DISALLOW_COPY_AND_ASSIGN(PostTaskAndReplyRelay);
+ // Not const to allow moving.
+ scoped_refptr<SequencedTaskRunner> reply_task_runner_;
};
} // namespace
@@ -116,10 +141,23 @@
DCHECK(task) << from_here.ToString();
DCHECK(reply) << from_here.ToString();
- return PostTask(from_here,
- BindOnce(&PostTaskAndReplyRelay::RunTaskAndPostReply,
- PostTaskAndReplyRelay(from_here, std::move(task),
- std::move(reply))));
+ const bool has_sequenced_context = SequencedTaskRunner::HasCurrentDefault();
+
+ const bool post_task_success = PostTask(
+ from_here, BindOnce(&PostTaskAndReplyRelay::RunTaskAndPostReply,
+ PostTaskAndReplyRelay(
+ from_here, std::move(task), std::move(reply),
+ has_sequenced_context
+ ? SequencedTaskRunner::GetCurrentDefault()
+ : nullptr)));
+
+ // PostTaskAndReply() requires a SequencedTaskRunner::CurrentDefaultHandle to
+ // post the reply. Having no SequencedTaskRunner::CurrentDefaultHandle is
+ // allowed when posting the task fails, to simplify calls during shutdown
+ // (https://crbug.com/922938).
+ CHECK(has_sequenced_context || !post_task_success);
+
+ return post_task_success;
}
} // namespace internal
diff --git a/base/threading/post_task_and_reply_impl.h b/base/threading/post_task_and_reply_impl.h
index 388eb11..9e3fda1 100644
--- a/base/threading/post_task_and_reply_impl.h
+++ b/base/threading/post_task_and_reply_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -8,7 +8,7 @@
#define BASE_THREADING_POST_TASK_AND_REPLY_IMPL_H_
#include "base/base_export.h"
-#include "base/callback.h"
+#include "base/functional/callback.h"
#include "base/location.h"
namespace base {
@@ -18,18 +18,19 @@
// custom execution context.
//
// If you're looking for a concrete implementation of PostTaskAndReply, you
-// probably want base::TaskRunner or base/task/post_task.h
+// probably want a base::TaskRunner (typically obtained from
+// base/task/thread_pool.h).
class BASE_EXPORT PostTaskAndReplyImpl {
public:
virtual ~PostTaskAndReplyImpl() = default;
// Posts |task| by calling PostTask(). On completion, posts |reply| to the
// origin sequence. Can only be called when
- // SequencedTaskRunnerHandle::IsSet(). Each callback is deleted synchronously
- // after running, or scheduled for asynchronous deletion on the origin
- // sequence if it can't run (e.g. if a TaskRunner skips it on shutdown). See
- // SequencedTaskRunner::DeleteSoon() for when objects scheduled for
- // asynchronous deletion can be leaked. Note: All //base task posting APIs
+ // SequencedTaskRunner::HasCurrentDefault(). Each callback is deleted
+ // synchronously after running, or scheduled for asynchronous deletion on the
+ // origin sequence if it can't run (e.g. if a TaskRunner skips it on
+ // shutdown). See SequencedTaskRunner::DeleteSoon() for when objects scheduled
+ // for asynchronous deletion can be leaked. Note: All //base task posting APIs
// require callbacks to support deletion on the posting sequence if they can't
// be scheduled.
bool PostTaskAndReply(const Location& from_here,
diff --git a/base/threading/post_task_and_reply_impl_unittest.cc b/base/threading/post_task_and_reply_impl_unittest.cc
index 319327d..e90ea9f 100644
--- a/base/threading/post_task_and_reply_impl_unittest.cc
+++ b/base/threading/post_task_and_reply_impl_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
+// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -7,10 +7,11 @@
#include <utility>
#include "base/auto_reset.h"
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/macros.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
+#include "base/task/sequenced_task_runner.h"
#include "base/test/test_mock_time_task_runner.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -33,7 +34,7 @@
}
// Non-owning.
- TaskRunner* const destination_;
+ const raw_ptr<TaskRunner> destination_;
};
class ObjectToDelete : public RefCounted<ObjectToDelete> {
@@ -43,24 +44,25 @@
EXPECT_FALSE(*delete_flag_);
}
+ ObjectToDelete(const ObjectToDelete&) = delete;
+ ObjectToDelete& operator=(const ObjectToDelete&) = delete;
+
private:
friend class RefCounted<ObjectToDelete>;
~ObjectToDelete() { *delete_flag_ = true; }
- bool* const delete_flag_;
-
- DISALLOW_COPY_AND_ASSIGN(ObjectToDelete);
+ const raw_ptr<bool> delete_flag_;
};
class MockObject {
public:
MockObject() = default;
+ MockObject(const MockObject&) = delete;
+ MockObject& operator=(const MockObject&) = delete;
+
MOCK_METHOD1(Task, void(scoped_refptr<ObjectToDelete>));
MOCK_METHOD1(Reply, void(scoped_refptr<ObjectToDelete>));
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockObject);
};
class MockRunsTasksInCurrentSequenceTaskRunner : public TestMockTimeTaskRunner {
@@ -70,6 +72,13 @@
TestMockTimeTaskRunner::Type::kStandalone)
: TestMockTimeTaskRunner(type) {}
+ MockRunsTasksInCurrentSequenceTaskRunner(
+ const MockRunsTasksInCurrentSequenceTaskRunner&) = delete;
+ MockRunsTasksInCurrentSequenceTaskRunner& operator=(
+ const MockRunsTasksInCurrentSequenceTaskRunner&) = delete;
+
+ void StopAcceptingTasks() { accepts_tasks_ = false; }
+
void RunUntilIdleWithRunsTasksInCurrentSequence() {
AutoReset<bool> reset(&runs_tasks_in_current_sequence_, true);
RunUntilIdle();
@@ -85,28 +94,44 @@
return runs_tasks_in_current_sequence_;
}
+ bool PostDelayedTask(const Location& from_here,
+ OnceClosure task,
+ TimeDelta delay) override {
+ if (!accepts_tasks_)
+ return false;
+
+ return TestMockTimeTaskRunner::PostDelayedTask(from_here, std::move(task),
+ delay);
+ }
+
private:
~MockRunsTasksInCurrentSequenceTaskRunner() override = default;
+ bool accepts_tasks_ = true;
bool runs_tasks_in_current_sequence_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(MockRunsTasksInCurrentSequenceTaskRunner);
};
class PostTaskAndReplyImplTest : public testing::Test {
+ public:
+ PostTaskAndReplyImplTest(const PostTaskAndReplyImplTest&) = delete;
+ PostTaskAndReplyImplTest& operator=(const PostTaskAndReplyImplTest&) = delete;
+
protected:
PostTaskAndReplyImplTest() = default;
- void PostTaskAndReplyToMockObject() {
+ bool PostTaskAndReplyToMockObject() {
+ return PostTaskAndReplyTaskRunner(post_runner_.get())
+ .PostTaskAndReply(
+ FROM_HERE,
+ BindOnce(&MockObject::Task, Unretained(&mock_object_),
+ MakeRefCounted<ObjectToDelete>(&delete_task_flag_)),
+ BindOnce(&MockObject::Reply, Unretained(&mock_object_),
+ MakeRefCounted<ObjectToDelete>(&delete_reply_flag_)));
+ }
+
+ void ExpectPostTaskAndReplyToMockObjectSucceeds() {
// Expect the post to succeed.
- EXPECT_TRUE(
- PostTaskAndReplyTaskRunner(post_runner_.get())
- .PostTaskAndReply(
- FROM_HERE,
- BindOnce(&MockObject::Task, Unretained(&mock_object_),
- MakeRefCounted<ObjectToDelete>(&delete_task_flag_)),
- BindOnce(&MockObject::Reply, Unretained(&mock_object_),
- MakeRefCounted<ObjectToDelete>(&delete_reply_flag_))));
+ EXPECT_TRUE(PostTaskAndReplyToMockObject());
// Expect the first task to be posted to |post_runner_|.
EXPECT_TRUE(post_runner_->HasPendingTask());
@@ -123,15 +148,12 @@
testing::StrictMock<MockObject> mock_object_;
bool delete_task_flag_ = false;
bool delete_reply_flag_ = false;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(PostTaskAndReplyImplTest);
};
} // namespace
TEST_F(PostTaskAndReplyImplTest, PostTaskAndReply) {
- PostTaskAndReplyToMockObject();
+ ExpectPostTaskAndReplyToMockObjectSucceeds();
EXPECT_CALL(mock_object_, Task(_));
post_runner_->RunUntilIdleWithRunsTasksInCurrentSequence();
@@ -157,7 +179,7 @@
}
TEST_F(PostTaskAndReplyImplTest, TaskDoesNotRun) {
- PostTaskAndReplyToMockObject();
+ ExpectPostTaskAndReplyToMockObjectSucceeds();
// Clear the |post_runner_|. Both callbacks should be scheduled for deletion
// on the |reply_runner_|.
@@ -174,7 +196,7 @@
}
TEST_F(PostTaskAndReplyImplTest, ReplyDoesNotRun) {
- PostTaskAndReplyToMockObject();
+ ExpectPostTaskAndReplyToMockObjectSucceeds();
EXPECT_CALL(mock_object_, Task(_));
post_runner_->RunUntilIdleWithRunsTasksInCurrentSequence();
@@ -194,5 +216,20 @@
EXPECT_TRUE(delete_reply_flag_);
}
+// This is a regression test for crbug.com/922938.
+TEST_F(PostTaskAndReplyImplTest,
+ PostTaskToStoppedTaskRunnerWithoutSequencedContext) {
+ reply_runner_.reset();
+ EXPECT_FALSE(SequencedTaskRunner::HasCurrentDefault());
+ post_runner_->StopAcceptingTasks();
+
+ // Expect the post to return false, but not to crash.
+ EXPECT_FALSE(PostTaskAndReplyToMockObject());
+
+ // Expect all tasks to be deleted.
+ EXPECT_TRUE(delete_task_flag_);
+ EXPECT_TRUE(delete_reply_flag_);
+}
+
} // namespace internal
} // namespace base
diff --git a/base/threading/scoped_blocking_call.cc b/base/threading/scoped_blocking_call.cc
index 127bc11..e1c3679 100644
--- a/base/threading/scoped_blocking_call.cc
+++ b/base/threading/scoped_blocking_call.cc
@@ -1,91 +1,130 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/scoped_blocking_call.h"
#include "base/lazy_instance.h"
-#include "base/scoped_clear_last_error.h"
#include "base/threading/thread_local.h"
#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/trace_event/base_tracing.h"
+#include "base/tracing_buildflags.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(ENABLE_BASE_TRACING)
+#include "third_party/perfetto/protos/perfetto/trace/track_event/source_location.pbzero.h" // nogncheck
+#endif // BUILDFLAG(ENABLE_BASE_TRACING)
+
+#if DCHECK_IS_ON()
+#include "base/auto_reset.h"
+#include "third_party/abseil-cpp/absl/base/attributes.h"
+#if defined(STARBOARD)
+#include <pthread.h>
+
+#include "base/check_op.h"
+#include "starboard/thread.h"
+#endif
+#endif
namespace base {
namespace {
-LazyInstance<ThreadLocalPointer<internal::BlockingObserver>>::Leaky
- tls_blocking_observer = LAZY_INSTANCE_INITIALIZER;
+#if DCHECK_IS_ON()
+#if defined(STARBOARD)
+ABSL_CONST_INIT pthread_once_t s_once_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_key = 0;
-// Last ScopedBlockingCall instantiated on this thread.
-LazyInstance<ThreadLocalPointer<internal::UncheckedScopedBlockingCall>>::Leaky
- tls_last_scoped_blocking_call = LAZY_INSTANCE_INITIALIZER;
+void InitThreadLocalKey() {
+ int res = pthread_key_create(&s_thread_local_key , NULL);
+ DCHECK(res == 0);
+}
+
+void EnsureThreadLocalKeyInited() {
+ pthread_once(&s_once_flag, InitThreadLocalKey);
+}
+
+bool GetConstructionInProgress() {
+ EnsureThreadLocalKeyInited();
+ void* construction_in_progress = pthread_getspecific(s_thread_local_key);
+ return !!construction_in_progress ? reinterpret_cast<intptr_t>(construction_in_progress) != 0 : false;
+}
+#else
+// Used to verify that the trace events used in the constructor do not result in
+// instantiating a ScopedBlockingCall themselves (which would cause an infinite
+// reentrancy loop).
+ABSL_CONST_INIT thread_local bool construction_in_progress = false;
+#endif
+#endif
} // namespace
-namespace internal {
+ScopedBlockingCall::ScopedBlockingCall(const Location& from_here,
+ BlockingType blocking_type)
+ : UncheckedScopedBlockingCall(
+ blocking_type,
+ UncheckedScopedBlockingCall::BlockingCallType::kRegular) {
+#if DCHECK_IS_ON()
+#if defined(STARBOARD)
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, reinterpret_cast<void*>(static_cast<intptr_t>(true)));
+#else
+ const AutoReset<bool> resetter(&construction_in_progress, true, false);
+#endif
+#endif
-UncheckedScopedBlockingCall::UncheckedScopedBlockingCall(
- BlockingType blocking_type)
- : blocking_observer_(tls_blocking_observer.Get().Get()),
- previous_scoped_blocking_call_(tls_last_scoped_blocking_call.Get().Get()),
- is_will_block_(blocking_type == BlockingType::WILL_BLOCK ||
- (previous_scoped_blocking_call_ &&
- previous_scoped_blocking_call_->is_will_block_)) {
- tls_last_scoped_blocking_call.Get().Set(this);
+ internal::AssertBlockingAllowed();
+ TRACE_EVENT_BEGIN(
+ "base", "ScopedBlockingCall", [&](perfetto::EventContext ctx) {
+ ctx.event()->set_source_location_iid(
+ base::trace_event::InternedSourceLocation::Get(&ctx, from_here));
+ });
- if (blocking_observer_) {
- if (!previous_scoped_blocking_call_) {
- blocking_observer_->BlockingStarted(blocking_type);
- } else if (blocking_type == BlockingType::WILL_BLOCK &&
- !previous_scoped_blocking_call_->is_will_block_) {
- blocking_observer_->BlockingTypeUpgraded();
- }
- }
+#if DCHECK_IS_ON() && defined(STARBOARD)
+ pthread_setspecific(s_thread_local_key, reinterpret_cast<void*>(static_cast<intptr_t>(false)));
+#endif
}
-UncheckedScopedBlockingCall::~UncheckedScopedBlockingCall() {
- // TLS affects result of GetLastError() on Windows. ScopedClearLastError
- // prevents side effect.
- base::internal::ScopedClearLastError save_last_error;
- DCHECK_EQ(this, tls_last_scoped_blocking_call.Get().Get());
- tls_last_scoped_blocking_call.Get().Set(previous_scoped_blocking_call_);
- if (blocking_observer_ && !previous_scoped_blocking_call_)
- blocking_observer_->BlockingEnded();
-}
-
-} // namespace internal
-
-ScopedBlockingCall::ScopedBlockingCall(BlockingType blocking_type)
- : UncheckedScopedBlockingCall(blocking_type) {
- base::AssertBlockingAllowed();
+ScopedBlockingCall::~ScopedBlockingCall() {
+ TRACE_EVENT_END("base");
}
namespace internal {
ScopedBlockingCallWithBaseSyncPrimitives::
- ScopedBlockingCallWithBaseSyncPrimitives(BlockingType blocking_type)
- : UncheckedScopedBlockingCall(blocking_type) {
+ ScopedBlockingCallWithBaseSyncPrimitives(const Location& from_here,
+ BlockingType blocking_type)
+ : UncheckedScopedBlockingCall(
+ blocking_type,
+ UncheckedScopedBlockingCall::BlockingCallType::kBaseSyncPrimitives) {
+#if DCHECK_IS_ON()
+#if defined(STARBOARD)
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, reinterpret_cast<void*>(static_cast<intptr_t>(true)));
+#else
+ const AutoReset<bool> resetter(&construction_in_progress, true, false);
+#endif
+#endif
+
internal::AssertBaseSyncPrimitivesAllowed();
+ TRACE_EVENT_BEGIN(
+ "base", "ScopedBlockingCallWithBaseSyncPrimitives",
+ [&](perfetto::EventContext ctx) {
+ perfetto::protos::pbzero::SourceLocation* source_location_data =
+ ctx.event()->set_source_location();
+ source_location_data->set_file_name(from_here.file_name());
+ source_location_data->set_function_name(from_here.function_name());
+ });
+
+#if DCHECK_IS_ON() && defined(STARBOARD)
+ pthread_setspecific(s_thread_local_key, reinterpret_cast<void*>(static_cast<intptr_t>(false)));
+#endif
}
-void SetBlockingObserverForCurrentThread(BlockingObserver* blocking_observer) {
- DCHECK(!tls_blocking_observer.Get().Get());
- tls_blocking_observer.Get().Set(blocking_observer);
-}
-
-void ClearBlockingObserverForTesting() {
- tls_blocking_observer.Get().Set(nullptr);
-}
-
-ScopedClearBlockingObserverForTesting::ScopedClearBlockingObserverForTesting()
- : blocking_observer_(tls_blocking_observer.Get().Get()) {
- tls_blocking_observer.Get().Set(nullptr);
-}
-
-ScopedClearBlockingObserverForTesting::
- ~ScopedClearBlockingObserverForTesting() {
- DCHECK(!tls_blocking_observer.Get().Get());
- tls_blocking_observer.Get().Set(blocking_observer_);
+ScopedBlockingCallWithBaseSyncPrimitives::
+ ~ScopedBlockingCallWithBaseSyncPrimitives() {
+ TRACE_EVENT_END("base");
}
} // namespace internal
diff --git a/base/threading/scoped_blocking_call.h b/base/threading/scoped_blocking_call.h
index 516ac0a..1f37ca2 100644
--- a/base/threading/scoped_blocking_call.h
+++ b/base/threading/scoped_blocking_call.h
@@ -1,12 +1,16 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef BASE_THREADING_SCOPED_BLOCKING_CALL_H
-#define BASE_THREADING_SCOPED_BLOCKING_CALL_H
+#ifndef BASE_THREADING_SCOPED_BLOCKING_CALL_H_
+#define BASE_THREADING_SCOPED_BLOCKING_CALL_H_
#include "base/base_export.h"
-#include "base/logging.h"
+#include "base/functional/callback_forward.h"
+#include "base/location.h"
+#include "base/strings/string_piece.h"
+#include "base/threading/scoped_blocking_call_internal.h"
+#include "base/types/strong_alias.h"
namespace base {
@@ -26,32 +30,6 @@
WILL_BLOCK
};
-namespace internal {
-
-class BlockingObserver;
-
-// Common implementation class for both ScopedBlockingCall and
-// ScopedBlockingCallWithBaseSyncPrimitives without assertions.
-class BASE_EXPORT UncheckedScopedBlockingCall {
- public:
- UncheckedScopedBlockingCall(BlockingType blocking_type);
- ~UncheckedScopedBlockingCall();
-
- private:
- internal::BlockingObserver* const blocking_observer_;
-
- // Previous ScopedBlockingCall instantiated on this thread.
- UncheckedScopedBlockingCall* const previous_scoped_blocking_call_;
-
- // Whether the BlockingType of the current thread was WILL_BLOCK after this
- // ScopedBlockingCall was instantiated.
- const bool is_will_block_;
-
- DISALLOW_COPY_AND_ASSIGN(UncheckedScopedBlockingCall);
-};
-
-} // namespace internal
-
// This class must be instantiated in every scope where a blocking call is made
// and serves as a precise annotation of the scope that may/will block for the
// scheduler. When a ScopedBlockingCall is instantiated, it asserts that
@@ -66,13 +44,15 @@
// Good:
// Data data;
// {
-// ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+// ScopedBlockingCall scoped_blocking_call(
+// FROM_HERE, BlockingType::WILL_BLOCK);
// data = GetDataFromNetwork();
// }
// CPUIntensiveProcessing(data);
//
// Bad:
-// ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+// ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+// BlockingType::WILL_BLOCK);
// Data data = GetDataFromNetwork();
// CPUIntensiveProcessing(data); // CPU usage within a ScopedBlockingCall.
//
@@ -80,7 +60,8 @@
// Data a;
// Data b;
// {
-// ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+// ScopedBlockingCall scoped_blocking_call(
+// FROM_HERE, BlockingType::MAY_BLOCK);
// a = GetDataFromMemoryCacheOrNetwork();
// b = GetDataFromMemoryCacheOrNetwork();
// }
@@ -88,7 +69,8 @@
// CPUIntensiveProcessing(b);
//
// Bad:
-// ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+// ScopedBlockingCall scoped_blocking_call(
+// FROM_HERE, BlockingType::MAY_BLOCK);
// Data a = GetDataFromMemoryCacheOrNetwork();
// Data b = GetDataFromMemoryCacheOrNetwork();
// CPUIntensiveProcessing(a); // CPU usage within a ScopedBlockingCall.
@@ -100,19 +82,21 @@
//
// Bad:
// base::WaitableEvent waitable_event(...);
-// ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+// ScopedBlockingCall scoped_blocking_call(
+// FROM_HERE, BlockingType::WILL_BLOCK);
// waitable_event.Wait(); // Wait() instantiates its own ScopedBlockingCall.
//
-// When a ScopedBlockingCall is instantiated from a TaskScheduler parallel or
+// When a ScopedBlockingCall is instantiated from a ThreadPool parallel or
// sequenced task, the thread pool size is incremented to compensate for the
// blocked thread (more or less aggressively depending on BlockingType).
-class BASE_EXPORT ScopedBlockingCall
+class BASE_EXPORT [[nodiscard]] ScopedBlockingCall
: public internal::UncheckedScopedBlockingCall {
public:
- ScopedBlockingCall(BlockingType blocking_type);
- ~ScopedBlockingCall() = default;
+ ScopedBlockingCall(const Location& from_here, BlockingType blocking_type);
+ ~ScopedBlockingCall();
};
+// Usage reserved for //base callers.
namespace internal {
// This class must be instantiated in every scope where a sync primitive is
@@ -120,56 +104,35 @@
// asserts that sync primitives are allowed in its scope with a call to
// internal::AssertBaseSyncPrimitivesAllowed(). The same guidelines as for
// ScopedBlockingCall should be followed.
-class BASE_EXPORT ScopedBlockingCallWithBaseSyncPrimitives
+class BASE_EXPORT [[nodiscard]] ScopedBlockingCallWithBaseSyncPrimitives
: public UncheckedScopedBlockingCall {
public:
- ScopedBlockingCallWithBaseSyncPrimitives(BlockingType blocking_type);
- ~ScopedBlockingCallWithBaseSyncPrimitives() = default;
-};
-
-// Interface for an observer to be informed when a thread enters or exits
-// the scope of ScopedBlockingCall objects.
-class BASE_EXPORT BlockingObserver {
- public:
- virtual ~BlockingObserver() = default;
-
- // Invoked when a ScopedBlockingCall is instantiated on the observed thread
- // where there wasn't an existing ScopedBlockingCall.
- virtual void BlockingStarted(BlockingType blocking_type) = 0;
-
- // Invoked when a WILL_BLOCK ScopedBlockingCall is instantiated on the
- // observed thread where there was a MAY_BLOCK ScopedBlockingCall but not a
- // WILL_BLOCK ScopedBlockingCall.
- virtual void BlockingTypeUpgraded() = 0;
-
- // Invoked when the last ScopedBlockingCall on the observed thread is
- // destroyed.
- virtual void BlockingEnded() = 0;
-};
-
-// Registers |blocking_observer| on the current thread. It is invalid to call
-// this on a thread where there is an active ScopedBlockingCall.
-BASE_EXPORT void SetBlockingObserverForCurrentThread(
- BlockingObserver* blocking_observer);
-
-BASE_EXPORT void ClearBlockingObserverForTesting();
-
-// Unregisters the |blocking_observer| on the current thread within its scope.
-// Used in TaskScheduler tests to prevent calls to //base sync primitives from
-// affecting the thread pool capacity.
-class BASE_EXPORT ScopedClearBlockingObserverForTesting {
- public:
- ScopedClearBlockingObserverForTesting();
- ~ScopedClearBlockingObserverForTesting();
-
- private:
- BlockingObserver* const blocking_observer_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedClearBlockingObserverForTesting);
+ ScopedBlockingCallWithBaseSyncPrimitives(const Location& from_here,
+ BlockingType blocking_type);
+ ~ScopedBlockingCallWithBaseSyncPrimitives();
};
} // namespace internal
+using IOJankReportingCallback =
+ RepeatingCallback<void(int janky_intervals_per_minute,
+ int total_janks_per_minute)>;
+using OnlyObservedThreadsForTest =
+ StrongAlias<class OnlyObservedThreadsTag, bool>;
+// Enables IO jank monitoring and reporting for this process. Should be called
+// at most once per process and only if
+// base::TimeTicks::IsConsistentAcrossProcesses() (the algorithm is unsafe
+// otherwise). |reporting_callback| will be invoked each time a monitoring
+// window completes, see internal::~IOJankMonitoringWindow() for details
+// (must be thread-safe). |only_observed_threads| can be set to true to have
+// the IOJank implementation ignore ScopedBlockingCalls on threads without a
+// BlockingObserver in tests that need to deterministically observe
+// ScopedBlockingCall side-effects.
+void BASE_EXPORT EnableIOJankMonitoringForProcess(
+ IOJankReportingCallback reporting_callback,
+ OnlyObservedThreadsForTest only_observed_threads =
+ OnlyObservedThreadsForTest(false));
+
} // namespace base
-#endif // BASE_THREADING_SCOPED_BLOCKING_CALL_H
+#endif // BASE_THREADING_SCOPED_BLOCKING_CALL_H_
diff --git a/base/threading/scoped_blocking_call_internal.cc b/base/threading/scoped_blocking_call_internal.cc
new file mode 100644
index 0000000..adaaf17
--- /dev/null
+++ b/base/threading/scoped_blocking_call_internal.cc
@@ -0,0 +1,457 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/scoped_blocking_call_internal.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/check_op.h"
+#include "base/compiler_specific.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/no_destructor.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/scoped_clear_last_error.h"
+#include "base/task/scoped_set_task_priority_for_current_thread.h"
+#include "base/task/thread_pool.h"
+#include "base/task/thread_pool/environment_config.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/base/attributes.h"
+
+#if defined(STARBOARD)
+#include <pthread.h>
+
+#include "starboard/thread.h"
+#endif
+
+namespace base {
+namespace internal {
+
+namespace {
+
+#if defined(STARBOARD)
+ABSL_CONST_INIT pthread_once_t s_once_observer_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_observer_key = 0;
+ABSL_CONST_INIT pthread_once_t s_once_call_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_call_key = 0;
+
+void InitThreadLocalObserverKey() {
+ int res = pthread_key_create(&s_thread_local_observer_key , NULL);
+ DCHECK(res == 0);
+}
+
+void InitThreadLocalCallKey() {
+ int res = pthread_key_create(&s_thread_local_call_key , NULL);
+ DCHECK(res == 0);
+}
+
+void EnsureThreadLocalObserverKeyInited() {
+ pthread_once(&s_once_observer_flag, InitThreadLocalObserverKey);
+}
+
+void EnsureThreadLocalCallKeyInited() {
+ pthread_once(&s_once_call_flag, InitThreadLocalCallKey);
+}
+#else
+ABSL_CONST_INIT thread_local BlockingObserver* blocking_observer = nullptr;
+
+// Last ScopedBlockingCall instantiated on this thread.
+ABSL_CONST_INIT thread_local UncheckedScopedBlockingCall*
+ last_scoped_blocking_call = nullptr;
+#endif
+
+// These functions can be removed, and the calls below replaced with direct
+// variable accesses, once the MSAN workaround is not necessary.
+BlockingObserver* GetBlockingObserver() {
+#if defined(STARBOARD)
+ EnsureThreadLocalObserverKeyInited();
+ return static_cast<BlockingObserver*>(
+ pthread_getspecific(s_thread_local_observer_key));
+#else
+ // Workaround false-positive MSAN use-of-uninitialized-value on
+ // thread_local storage for loaded libraries:
+ // https://github.com/google/sanitizers/issues/1265
+ MSAN_UNPOISON(&blocking_observer, sizeof(BlockingObserver*));
+
+ return blocking_observer;
+#endif
+}
+UncheckedScopedBlockingCall* GetLastScopedBlockingCall() {
+#if defined(STARBOARD)
+ EnsureThreadLocalCallKeyInited();
+ return static_cast<UncheckedScopedBlockingCall*>(
+ pthread_getspecific(s_thread_local_call_key));
+#else
+ // Workaround false-positive MSAN use-of-uninitialized-value on
+ // thread_local storage for loaded libraries:
+ // https://github.com/google/sanitizers/issues/1265
+ MSAN_UNPOISON(&last_scoped_blocking_call,
+ sizeof(UncheckedScopedBlockingCall*));
+
+ return last_scoped_blocking_call;
+#endif
+}
+
+// Set to true by scoped_blocking_call_unittest to ensure unrelated threads
+// entering ScopedBlockingCalls don't affect test outcomes.
+bool g_only_monitor_observed_threads = false;
+
+bool IsBackgroundPriorityWorker() {
+ return GetTaskPriorityForCurrentThread() == TaskPriority::BEST_EFFORT &&
+ CanUseBackgroundThreadTypeForWorkerThread();
+}
+
+} // namespace
+
+void SetBlockingObserverForCurrentThread(
+ BlockingObserver* new_blocking_observer) {
+ DCHECK(!GetBlockingObserver());
+#if defined(STARBOARD)
+ EnsureThreadLocalObserverKeyInited();
+ pthread_setspecific(s_thread_local_observer_key, new_blocking_observer);
+#else
+ blocking_observer = new_blocking_observer;
+#endif
+}
+
+void ClearBlockingObserverForCurrentThread() {
+#if defined(STARBOARD)
+ EnsureThreadLocalObserverKeyInited();
+ pthread_setspecific(s_thread_local_observer_key, nullptr);
+#else
+ blocking_observer = nullptr;
+#endif
+}
+
+IOJankMonitoringWindow::ScopedMonitoredCall::ScopedMonitoredCall()
+ : call_start_(TimeTicks::Now()),
+ assigned_jank_window_(MonitorNextJankWindowIfNecessary(call_start_)) {
+ if (assigned_jank_window_ &&
+ call_start_ < assigned_jank_window_->start_time_) {
+ // Sampling |call_start_| and being assigned an IOJankMonitoringWindow is
+ // racy. It is possible that |call_start_| is sampled near the very end of
+ // the current window; meanwhile, another ScopedMonitoredCall on another
+ // thread samples a |call_start_| which lands in the next window. If that
+ // thread beats this one to MonitorNextJankWindowIfNecessary(), this thread
+ // will incorrectly be assigned that window (in the future w.r.t. to its
+ // |call_start_|). To avoid OOB-indexing in AddJank(), crbug.com/1209622, it
+ // is necessary to correct this by bumping |call_start_| to the received
+ // window's |start_time_|.
+ //
+ // Note: The alternate approach of getting |assigned_jank_window_| before
+ // |call_start_| has the opposite problem where |call_start_| can be more
+ // than kNumIntervals ahead of |start_time_| when sampling across the window
+ // boundary, resulting in OOB-indexing the other way. To solve that a loop
+ // would be required (re-getting the latest window and re-sampling
+ // |call_start_| until the condition holds). The loopless solution is thus
+ // preferred.
+ //
+ // A lock covering this entire constructor is also undesired because of the
+ // lock-free logic at the end of MonitorNextJankWindowIfNecessary().
+ call_start_ = assigned_jank_window_->start_time_;
+ }
+}
+
+IOJankMonitoringWindow::ScopedMonitoredCall::~ScopedMonitoredCall() {
+ if (assigned_jank_window_) {
+ assigned_jank_window_->OnBlockingCallCompleted(call_start_,
+ TimeTicks::Now());
+ }
+}
+
+void IOJankMonitoringWindow::ScopedMonitoredCall::Cancel() {
+ assigned_jank_window_ = nullptr;
+}
+
+IOJankMonitoringWindow::IOJankMonitoringWindow(TimeTicks start_time)
+ : start_time_(start_time) {}
+
+// static
+void IOJankMonitoringWindow::CancelMonitoringForTesting() {
+ g_only_monitor_observed_threads = false;
+ AutoLock lock(current_jank_window_lock());
+ current_jank_window_storage() = nullptr;
+ reporting_callback_storage() = NullCallback();
+}
+
+// static
+constexpr TimeDelta IOJankMonitoringWindow::kIOJankInterval;
+// static
+constexpr TimeDelta IOJankMonitoringWindow::kMonitoringWindow;
+// static
+constexpr TimeDelta IOJankMonitoringWindow::kTimeDiscrepancyTimeout;
+// static
+constexpr int IOJankMonitoringWindow::kNumIntervals;
+
+// static
+scoped_refptr<IOJankMonitoringWindow>
+IOJankMonitoringWindow::MonitorNextJankWindowIfNecessary(TimeTicks recent_now) {
+ DCHECK_GE(TimeTicks::Now(), recent_now);
+
+ scoped_refptr<IOJankMonitoringWindow> next_jank_window;
+
+ {
+ AutoLock lock(current_jank_window_lock());
+
+ if (!reporting_callback_storage())
+ return nullptr;
+
+ scoped_refptr<IOJankMonitoringWindow>& current_jank_window_ref =
+ current_jank_window_storage();
+
+ // Start the next window immediately after the current one (rather than
+ // based on Now() to avoid uncovered gaps). Only use Now() for the very
+ // first window in a monitoring chain.
+ TimeTicks next_window_start_time =
+ current_jank_window_ref
+ ? current_jank_window_ref->start_time_ + kMonitoringWindow
+ : recent_now;
+
+ if (next_window_start_time > recent_now) {
+ // Another thread beat us to constructing the next monitoring window and
+ // |current_jank_window_ref| already covers |recent_now|.
+ return current_jank_window_ref;
+ }
+
+ if (recent_now - next_window_start_time >= kTimeDiscrepancyTimeout) {
+ // If the delayed task runs on a regular heartbeat, |recent_now| should be
+ // roughly equal to |next_window_start_time|. If we miss by more than
+ // kTimeDiscrepancyTimeout, we likely hit machine sleep, cancel sampling
+ // that window in that case.
+ //
+ // Note: It is safe to touch |canceled_| without a lock here as this is
+ // the only time it's set and it naturally happens-before
+ // |current_jank_window_ref|'s destructor reads it.
+ current_jank_window_ref->canceled_ = true;
+ next_window_start_time = recent_now;
+ }
+
+ next_jank_window =
+ MakeRefCounted<IOJankMonitoringWindow>(next_window_start_time);
+
+ if (current_jank_window_ref && !current_jank_window_ref->canceled_) {
+ // If there are still IO operations in progress within
+ // |current_jank_window_ref|, they have a ref to it and will be the ones
+ // triggering ~IOJankMonitoringWindow(). When doing so, they will overlap
+ // into the |next_jank_window| we are setting up (|next_| will also own a
+ // ref so a very long jank can safely unwind across a chain of pending
+ // |next_|'s).
+ DCHECK(!current_jank_window_ref->next_);
+ current_jank_window_ref->next_ = next_jank_window;
+ }
+
+ // Make |next_jank_window| the new current before releasing the lock.
+ current_jank_window_ref = next_jank_window;
+ }
+
+ // Post a task to kick off the next monitoring window if no monitored thread
+ // beats us to it. Adjust the timing to alleviate any drift in the timer. Do
+ // this outside the lock to avoid scheduling tasks while holding it.
+ ThreadPool::PostDelayedTask(
+ FROM_HERE, BindOnce([]() {
+ IOJankMonitoringWindow::MonitorNextJankWindowIfNecessary(
+ TimeTicks::Now());
+ }),
+ kMonitoringWindow - (recent_now - next_jank_window->start_time_));
+
+ return next_jank_window;
+}
+
+// NO_THREAD_SAFETY_ANALYSIS because ~RefCountedThreadSafe() guarantees we're
+// the last ones to access this state (and ordered after all other accesses).
+IOJankMonitoringWindow::~IOJankMonitoringWindow() NO_THREAD_SAFETY_ANALYSIS {
+ if (canceled_)
+ return;
+
+ int janky_intervals_count = 0;
+ int total_jank_count = 0;
+
+ for (size_t interval_jank_count : intervals_jank_count_) {
+ if (interval_jank_count > 0) {
+ ++janky_intervals_count;
+ total_jank_count += interval_jank_count;
+ }
+ }
+
+ // reporting_callback_storage() is safe to access without lock because an
+ // IOJankMonitoringWindow existing means we're after the call to
+ // EnableIOJankMonitoringForProcess() and it will not change after that call.
+ DCHECK(reporting_callback_storage());
+ reporting_callback_storage().Run(janky_intervals_count, total_jank_count);
+}
+
+void IOJankMonitoringWindow::OnBlockingCallCompleted(TimeTicks call_start,
+ TimeTicks call_end) {
+ // Confirm we never hit a case of TimeTicks going backwards on the same thread
+ // nor of TimeTicks rolling over the int64_t boundary (which would break
+ // comparison operators).
+ DCHECK_LE(call_start, call_end);
+
+ if (call_end - call_start < kIOJankInterval)
+ return;
+
+ // Make sure the chain of |next_| pointers is sufficient to reach
+ // |call_end| (e.g. if this runs before the delayed task kicks in)
+ if (call_end >= start_time_ + kMonitoringWindow)
+ MonitorNextJankWindowIfNecessary(call_end);
+
+ // Begin attributing jank to the first interval in which it appeared, no
+ // matter how far into the interval the jank began.
+ const int jank_start_index =
+ ClampFloor((call_start - start_time_) / kIOJankInterval);
+
+ // Round the jank duration so the total number of intervals marked janky is as
+ // close as possible to the actual jank duration.
+ const int num_janky_intervals =
+ ClampRound((call_end - call_start) / kIOJankInterval);
+
+ AddJank(jank_start_index, num_janky_intervals);
+}
+
+void IOJankMonitoringWindow::AddJank(int local_jank_start_index,
+ int num_janky_intervals) {
+ DCHECK_GE(local_jank_start_index, 0);
+ DCHECK_LT(local_jank_start_index, kNumIntervals);
+
+ // Increment jank counts for intervals in this window. If
+ // |num_janky_intervals| lands beyond kNumIntervals, the additional intervals
+ // will be reported to |next_|.
+ const int jank_end_index = local_jank_start_index + num_janky_intervals;
+ const int local_jank_end_index = std::min(kNumIntervals, jank_end_index);
+
+ {
+ // Note: while this window could be |canceled| here we must add our count
+ // unconditionally as it is only thread-safe to read |canceled| in
+ // ~IOJankMonitoringWindow().
+ AutoLock lock(intervals_lock_);
+ for (int i = local_jank_start_index; i < local_jank_end_index; ++i)
+ ++intervals_jank_count_[i];
+ }
+
+ if (jank_end_index != local_jank_end_index) {
+ // OnBlockingCallCompleted() should have already ensured there's a |next_|
+ // chain covering |num_janky_intervals| unless it caused this to be
+ // |canceled_|. Exceptionally for this check, reading these fields when
+ // they're expected to be true is thread-safe as their only modification
+ // happened-before this point.
+ DCHECK(next_ || canceled_);
+ if (next_) {
+ // If |next_| is non-null, it means |this| wasn't canceled and it implies
+ // |next_| covers the time range starting immediately after this window.
+ DCHECK_EQ(next_->start_time_, start_time_ + kMonitoringWindow);
+ next_->AddJank(0, jank_end_index - local_jank_end_index);
+ }
+ }
+}
+
+// static
+Lock& IOJankMonitoringWindow::current_jank_window_lock() {
+ static NoDestructor<Lock> current_jank_window_lock;
+ return *current_jank_window_lock;
+}
+
+// static
+scoped_refptr<IOJankMonitoringWindow>&
+IOJankMonitoringWindow::current_jank_window_storage() {
+ static NoDestructor<scoped_refptr<IOJankMonitoringWindow>>
+ current_jank_window;
+ return *current_jank_window;
+}
+
+// static
+IOJankReportingCallback& IOJankMonitoringWindow::reporting_callback_storage() {
+ static NoDestructor<IOJankReportingCallback> reporting_callback;
+ return *reporting_callback;
+}
+
+UncheckedScopedBlockingCall::UncheckedScopedBlockingCall(
+ BlockingType blocking_type,
+ BlockingCallType blocking_call_type)
+ : blocking_observer_(GetBlockingObserver()),
+ previous_scoped_blocking_call_(GetLastScopedBlockingCall()),
+#if !defined(STARBOARD)
+ resetter_(&last_scoped_blocking_call, this),
+#endif
+ is_will_block_(blocking_type == BlockingType::WILL_BLOCK ||
+ (previous_scoped_blocking_call_ &&
+ previous_scoped_blocking_call_->is_will_block_)) {
+#if defined(STARBOARD)
+ EnsureThreadLocalCallKeyInited();
+ reset_to_ = pthread_getspecific(s_thread_local_call_key);
+ pthread_setspecific(s_thread_local_call_key, this);
+#endif
+
+ // Only monitor non-nested ScopedBlockingCall(MAY_BLOCK) calls on foreground
+ // threads. Cancels() any pending monitored call when a WILL_BLOCK or
+ // ScopedBlockingCallWithBaseSyncPrimitives nests into a
+ // ScopedBlockingCall(MAY_BLOCK).
+ if (!IsBackgroundPriorityWorker() &&
+ (!g_only_monitor_observed_threads || blocking_observer_)) {
+ const bool is_monitored_type =
+ blocking_call_type == BlockingCallType::kRegular && !is_will_block_;
+ if (is_monitored_type && !previous_scoped_blocking_call_) {
+ monitored_call_.emplace();
+ } else if (!is_monitored_type && previous_scoped_blocking_call_ &&
+ previous_scoped_blocking_call_->monitored_call_) {
+ previous_scoped_blocking_call_->monitored_call_->Cancel();
+ }
+ }
+
+ if (blocking_observer_) {
+ if (!previous_scoped_blocking_call_) {
+ blocking_observer_->BlockingStarted(blocking_type);
+ } else if (blocking_type == BlockingType::WILL_BLOCK &&
+ !previous_scoped_blocking_call_->is_will_block_) {
+ blocking_observer_->BlockingTypeUpgraded();
+ }
+ }
+}
+
+UncheckedScopedBlockingCall::~UncheckedScopedBlockingCall() {
+ // TLS affects result of GetLastError() on Windows. ScopedClearLastError
+ // prevents side effect.
+ ScopedClearLastError save_last_error;
+ DCHECK_EQ(this, GetLastScopedBlockingCall());
+ if (blocking_observer_ && !previous_scoped_blocking_call_)
+ blocking_observer_->BlockingEnded();
+
+#if defined(STARBOARD)
+ EnsureThreadLocalCallKeyInited();
+ pthread_setspecific(s_thread_local_call_key, reset_to_);
+#endif
+}
+
+} // namespace internal
+
+void EnableIOJankMonitoringForProcess(
+ IOJankReportingCallback reporting_callback,
+ OnlyObservedThreadsForTest only_observed_threads) {
+ {
+ AutoLock lock(internal::IOJankMonitoringWindow::current_jank_window_lock());
+
+ DCHECK(internal::IOJankMonitoringWindow::reporting_callback_storage()
+ .is_null());
+ internal::IOJankMonitoringWindow::reporting_callback_storage() =
+ std::move(reporting_callback);
+ }
+
+ if (only_observed_threads) {
+ internal::g_only_monitor_observed_threads = true;
+ } else {
+ // Do not set it to `false` when it already is as that causes data races in
+ // browser tests (which EnableIOJankMonitoringForProcess after ThreadPool is
+ // already running).
+ DCHECK(!internal::g_only_monitor_observed_threads);
+ }
+
+ // Make sure monitoring starts now rather than randomly at the next
+ // ScopedMonitoredCall construction.
+ internal::IOJankMonitoringWindow::MonitorNextJankWindowIfNecessary(
+ TimeTicks::Now());
+}
+
+} // namespace base
diff --git a/base/threading/scoped_blocking_call_internal.h b/base/threading/scoped_blocking_call_internal.h
new file mode 100644
index 0000000..0d5f418
--- /dev/null
+++ b/base/threading/scoped_blocking_call_internal.h
@@ -0,0 +1,212 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_
+#define BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_
+
+#include "base/auto_reset.h"
+#include "base/base_export.h"
+#include "base/functional/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
+#include "base/time/time.h"
+#include "base/types/strong_alias.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+
+// Forward-declare types from scoped_blocking_call.h to break cyclic dependency.
+enum class BlockingType;
+using IOJankReportingCallback = RepeatingCallback<void(int, int)>;
+using OnlyObservedThreadsForTest =
+ StrongAlias<class OnlyObservedThreadsTag, bool>;
+void BASE_EXPORT EnableIOJankMonitoringForProcess(IOJankReportingCallback,
+ OnlyObservedThreadsForTest);
+
+// Implementation details of types in scoped_blocking_call.h and classes for a
+// few key //base types to observe and react to blocking calls.
+namespace internal {
+
+// Interface for an observer to be informed when a thread enters or exits
+// the scope of ScopedBlockingCall objects.
+class BASE_EXPORT BlockingObserver {
+ public:
+ virtual ~BlockingObserver() = default;
+
+ // Invoked when a ScopedBlockingCall is instantiated on the observed thread
+ // where there wasn't an existing ScopedBlockingCall.
+ virtual void BlockingStarted(BlockingType blocking_type) = 0;
+
+ // Invoked when a WILL_BLOCK ScopedBlockingCall is instantiated on the
+ // observed thread where there was a MAY_BLOCK ScopedBlockingCall but not a
+ // WILL_BLOCK ScopedBlockingCall.
+ virtual void BlockingTypeUpgraded() = 0;
+
+ // Invoked when the last ScopedBlockingCall on the observed thread is
+ // destroyed.
+ virtual void BlockingEnded() = 0;
+};
+
+// Registers |new_blocking_observer| on the current thread. It is invalid to
+// call this on a thread where there is an active ScopedBlockingCall.
+BASE_EXPORT void SetBlockingObserverForCurrentThread(
+ BlockingObserver* new_blocking_observer);
+
+BASE_EXPORT void ClearBlockingObserverForCurrentThread();
+
+// An IOJankMonitoringWindow instruments 1-minute of runtime. Any I/O jank > 1
+// second happening during that period will be reported to it. It will then
+// report via the IOJankReportingCallback in |reporting_callback_storage()| if
+// it's non-null. https://bit.ly/chrome-io-jank-metric.
+class BASE_EXPORT [[maybe_unused, nodiscard]] IOJankMonitoringWindow
+ : public RefCountedThreadSafe<IOJankMonitoringWindow> {
+ public:
+ explicit IOJankMonitoringWindow(TimeTicks start_time);
+
+ IOJankMonitoringWindow(const IOJankMonitoringWindow&) = delete;
+ IOJankMonitoringWindow& operator=(const IOJankMonitoringWindow&) = delete;
+
+ // Cancels monitoring and clears this class' static state.
+ static void CancelMonitoringForTesting();
+
+ class [[maybe_unused, nodiscard]] ScopedMonitoredCall {
+ public:
+ // Stores a ref to the current IOJankMonitoringWindow if monitoring is
+ // active, keeping it alive at least until the monitored call completes or
+ // Cancel() is invoked.
+ ScopedMonitoredCall();
+
+ // Reports to |assigned_jank_window_| if it's non-null.
+ ~ScopedMonitoredCall();
+
+ ScopedMonitoredCall(const ScopedMonitoredCall&) = delete;
+ ScopedMonitoredCall& operator=(const ScopedMonitoredCall&) = delete;
+
+ // Cancels monitoring of this call.
+ void Cancel();
+
+ private:
+ TimeTicks call_start_;
+ scoped_refptr<IOJankMonitoringWindow> assigned_jank_window_;
+ };
+
+ static constexpr TimeDelta kIOJankInterval = Seconds(1);
+ static constexpr TimeDelta kMonitoringWindow = Minutes(1);
+ static constexpr TimeDelta kTimeDiscrepancyTimeout = kIOJankInterval * 10;
+ static constexpr int kNumIntervals = kMonitoringWindow / kIOJankInterval;
+
+ // kIOJankIntervals must integrally fill kMonitoringWindow
+ static_assert((kMonitoringWindow % kIOJankInterval).is_zero(), "");
+
+ // Cancelation is simple because it can only affect the current window.
+ static_assert(kTimeDiscrepancyTimeout < kMonitoringWindow, "");
+
+ private:
+ friend class base::RefCountedThreadSafe<IOJankMonitoringWindow>;
+ friend void base::EnableIOJankMonitoringForProcess(
+ IOJankReportingCallback,
+ OnlyObservedThreadsForTest);
+
+ // No-op if reporting_callback_storage() is null (i.e. unless
+ // EnableIOJankMonitoringForProcess() was called).
+ // When reporting_callback_storage() is non-null : Ensures that there's an
+ // active IOJankMonitoringWindow for Now(), connects it via |next_| to the
+ // previous IOJankMonitoringWindow to let ScopedMonitoredCalls that span
+ // multiple windows report to each window they cover. In the event that Now()
+ // is farther ahead than expected (> 10s), the previous window is |canceled_|
+ // as it was likely interrupted by a system sleep and a new
+ // IOJankMonitoringWindow chain is started from Now(). In all cases, returns a
+ // live reference to the current (old or new) IOJankMonitoringWindow as a
+ // helper so callers that need it don't need to re-acquire
+ // current_jank_window_lock() after calling this.
+ // |recent_now| is a recent sampling of TimeTicks::Now(), avoids
+ // double-sampling Now() from most callers.
+ static scoped_refptr<IOJankMonitoringWindow> MonitorNextJankWindowIfNecessary(
+ TimeTicks recent_now);
+
+ // An IOJankMonitoringWindow is destroyed when all refs to it are gone, i.e.:
+ // 1) The window it covers has elapsed and MonitorNextJankWindowIfNecessary()
+ // has replaced it.
+ // 2) All pending ScopedMonitoredCall's in their range have completed
+ // (including the ones that transitively have it in their |next_| chain).
+ ~IOJankMonitoringWindow();
+
+ // Called from ~ScopedMonitoredCall().
+ void OnBlockingCallCompleted(TimeTicks call_start, TimeTicks call_end);
+
+ // Helper for OnBlockingCallCompleted(). Records |num_janky_intervals|
+ // starting at |local_jank_start_index|. Having this logic separately helps
+ // sane management of |intervals_lock_| when recursive calls through |next_|
+ // pointers are necessary.
+ void AddJank(int local_jank_start_index, int num_janky_intervals);
+
+ static Lock& current_jank_window_lock();
+ static scoped_refptr<IOJankMonitoringWindow>& current_jank_window_storage()
+ EXCLUSIVE_LOCKS_REQUIRED(current_jank_window_lock());
+
+ // Storage for callback used to report monitoring results.
+ // NullCallback if monitoring was not enabled for this process.
+ static IOJankReportingCallback& reporting_callback_storage()
+ EXCLUSIVE_LOCKS_REQUIRED(current_jank_window_lock());
+
+ Lock intervals_lock_;
+ size_t intervals_jank_count_[kNumIntervals] GUARDED_BY(intervals_lock_) = {};
+
+ const TimeTicks start_time_;
+
+ // Set only once per window, in MonitorNextJankWindowIfNecessary(). Any read
+ // of this value must be ordered after that call in memory and in time.
+ scoped_refptr<IOJankMonitoringWindow> next_;
+
+ // Set to true if ~IOJankMonitoringWindow() shouldn't record metrics.
+ // Modifications of this variable must be synchronized with each other and
+ // happen-before ~IOJankMonitoringWindow().
+ bool canceled_ = false;
+};
+
+// Common implementation class for both ScopedBlockingCall and
+// ScopedBlockingCallWithBaseSyncPrimitives without assertions.
+class BASE_EXPORT [[maybe_unused, nodiscard]] UncheckedScopedBlockingCall {
+ public:
+ enum class BlockingCallType {
+ kRegular,
+ kBaseSyncPrimitives,
+ };
+
+ UncheckedScopedBlockingCall(BlockingType blocking_type,
+ BlockingCallType blocking_call_type);
+
+ UncheckedScopedBlockingCall(const UncheckedScopedBlockingCall&) = delete;
+ UncheckedScopedBlockingCall& operator=(const UncheckedScopedBlockingCall&) =
+ delete;
+
+ ~UncheckedScopedBlockingCall();
+
+ private:
+ const raw_ptr<BlockingObserver> blocking_observer_;
+
+ // Previous ScopedBlockingCall instantiated on this thread.
+ const raw_ptr<UncheckedScopedBlockingCall> previous_scoped_blocking_call_;
+
+#if defined(STARBOARD)
+ void* reset_to_;
+#else
+ const base::AutoReset<UncheckedScopedBlockingCall*> resetter_;
+#endif
+
+ // Whether the BlockingType of the current thread was WILL_BLOCK after this
+ // ScopedBlockingCall was instantiated.
+ const bool is_will_block_;
+
+ // Non-nullopt for non-nested blocking calls of type MAY_BLOCK on foreground
+ // threads which we monitor for I/O jank.
+ absl::optional<IOJankMonitoringWindow::ScopedMonitoredCall> monitored_call_;
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // BASE_THREADING_SCOPED_BLOCKING_CALL_INTERNAL_H_
diff --git a/base/threading/scoped_blocking_call_unittest.cc b/base/threading/scoped_blocking_call_unittest.cc
index 5e030f3..f4f29c6 100644
--- a/base/threading/scoped_blocking_call_unittest.cc
+++ b/base/threading/scoped_blocking_call_unittest.cc
@@ -1,15 +1,32 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/scoped_blocking_call.h"
#include <memory>
+#include <utility>
+#include <vector>
-#include "base/macros.h"
+#include "base/barrier_closure.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/task/thread_pool.h"
+#include "base/task/thread_pool/environment_config.h"
+#include "base/task/thread_pool/thread_pool_impl.h"
+#include "base/test/bind.h"
#include "base/test/gtest_util.h"
+#include "base/test/task_environment.h"
+#include "base/test/test_waitable_event.h"
+#include "base/threading/scoped_blocking_call_internal.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time_override.h"
+#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+using testing::ElementsAre;
namespace base {
@@ -19,54 +36,56 @@
public:
MockBlockingObserver() = default;
+ MockBlockingObserver(const MockBlockingObserver&) = delete;
+ MockBlockingObserver& operator=(const MockBlockingObserver&) = delete;
+
MOCK_METHOD1(BlockingStarted, void(BlockingType));
MOCK_METHOD0(BlockingTypeUpgraded, void());
MOCK_METHOD0(BlockingEnded, void());
-
- private:
- DISALLOW_COPY_AND_ASSIGN(MockBlockingObserver);
};
class ScopedBlockingCallTest : public testing::Test {
+ public:
+ ScopedBlockingCallTest(const ScopedBlockingCallTest&) = delete;
+ ScopedBlockingCallTest& operator=(const ScopedBlockingCallTest&) = delete;
+
protected:
ScopedBlockingCallTest() {
internal::SetBlockingObserverForCurrentThread(&observer_);
}
~ScopedBlockingCallTest() override {
- internal::ClearBlockingObserverForTesting();
+ internal::ClearBlockingObserverForCurrentThread();
}
testing::StrictMock<MockBlockingObserver> observer_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ScopedBlockingCallTest);
};
} // namespace
TEST_F(ScopedBlockingCallTest, MayBlock) {
EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
- ScopedBlockingCall scoped_blocking_call(BlockingType::MAY_BLOCK);
+ ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
EXPECT_CALL(observer_, BlockingEnded());
}
TEST_F(ScopedBlockingCallTest, WillBlock) {
EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
- ScopedBlockingCall scoped_blocking_call(BlockingType::WILL_BLOCK);
+ ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::WILL_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
EXPECT_CALL(observer_, BlockingEnded());
}
TEST_F(ScopedBlockingCallTest, MayBlockWillBlock) {
EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
- ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_a(FROM_HERE, BlockingType::MAY_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
{
EXPECT_CALL(observer_, BlockingTypeUpgraded());
- ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_b(FROM_HERE,
+ BlockingType::WILL_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
}
@@ -75,47 +94,61 @@
TEST_F(ScopedBlockingCallTest, WillBlockMayBlock) {
EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
- ScopedBlockingCall scoped_blocking_call_a(BlockingType::WILL_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_a(FROM_HERE,
+ BlockingType::WILL_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
- { ScopedBlockingCall scoped_blocking_call_b(BlockingType::MAY_BLOCK); }
+ {
+ ScopedBlockingCall scoped_blocking_call_b(FROM_HERE,
+ BlockingType::MAY_BLOCK);
+ }
EXPECT_CALL(observer_, BlockingEnded());
}
TEST_F(ScopedBlockingCallTest, MayBlockMayBlock) {
EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
- ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_a(FROM_HERE, BlockingType::MAY_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
- { ScopedBlockingCall scoped_blocking_call_b(BlockingType::MAY_BLOCK); }
+ {
+ ScopedBlockingCall scoped_blocking_call_b(FROM_HERE,
+ BlockingType::MAY_BLOCK);
+ }
EXPECT_CALL(observer_, BlockingEnded());
}
TEST_F(ScopedBlockingCallTest, WillBlockWillBlock) {
EXPECT_CALL(observer_, BlockingStarted(BlockingType::WILL_BLOCK));
- ScopedBlockingCall scoped_blocking_call_a(BlockingType::WILL_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_a(FROM_HERE,
+ BlockingType::WILL_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
- { ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK); }
+ {
+ ScopedBlockingCall scoped_blocking_call_b(FROM_HERE,
+ BlockingType::WILL_BLOCK);
+ }
EXPECT_CALL(observer_, BlockingEnded());
}
TEST_F(ScopedBlockingCallTest, MayBlockWillBlockTwice) {
EXPECT_CALL(observer_, BlockingStarted(BlockingType::MAY_BLOCK));
- ScopedBlockingCall scoped_blocking_call_a(BlockingType::MAY_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_a(FROM_HERE, BlockingType::MAY_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
{
EXPECT_CALL(observer_, BlockingTypeUpgraded());
- ScopedBlockingCall scoped_blocking_call_b(BlockingType::WILL_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_b(FROM_HERE,
+ BlockingType::WILL_BLOCK);
testing::Mock::VerifyAndClear(&observer_);
{
- ScopedBlockingCall scoped_blocking_call_c(BlockingType::MAY_BLOCK);
- ScopedBlockingCall scoped_blocking_call_d(BlockingType::WILL_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_c(FROM_HERE,
+ BlockingType::MAY_BLOCK);
+ ScopedBlockingCall scoped_blocking_call_d(FROM_HERE,
+ BlockingType::WILL_BLOCK);
}
}
@@ -124,11 +157,810 @@
TEST(ScopedBlockingCallDestructionOrderTest, InvalidDestructionOrder) {
auto scoped_blocking_call_a =
- std::make_unique<ScopedBlockingCall>(BlockingType::WILL_BLOCK);
+ std::make_unique<ScopedBlockingCall>(FROM_HERE, BlockingType::WILL_BLOCK);
auto scoped_blocking_call_b =
- std::make_unique<ScopedBlockingCall>(BlockingType::WILL_BLOCK);
+ std::make_unique<ScopedBlockingCall>(FROM_HERE, BlockingType::WILL_BLOCK);
EXPECT_DCHECK_DEATH({ scoped_blocking_call_a.reset(); });
}
+namespace {
+
+class ScopedBlockingCallIOJankMonitoringTest : public testing::Test {
+ public:
+ explicit ScopedBlockingCallIOJankMonitoringTest(
+ test::TaskEnvironment::TimeSource time_source =
+ test::TaskEnvironment::TimeSource::MOCK_TIME)
+ : task_environment_(absl::in_place, time_source) {}
+
+ void SetUp() override {
+ // Note 1: While EnableIOJankMonitoringForProcess() is documented as being
+ // only callable once per process. The call to CancelMonitoringForTesting()
+ // in TearDown() makes it okay to call this in multiple tests in a row
+ // within a single process.
+ // Note 2: No need to check TimeTicks::IsConsistentAcrossProcesses() in
+ // spite of EnableIOJankMonitoringForProcess()'s requirement as
+ // TimeSource::MOCK_TIME avoids usage of the system clock and avoids the
+ // issue.
+ // OnlyObservedThreadsForTest(true) to prevent flakes which are believed to
+ // be caused by ScopedBlockingCall interference in the same process but
+ // outside this test's managed threads: crbug.com/1071166.
+ EnableIOJankMonitoringForProcess(
+ BindLambdaForTesting([&](int janky_intervals_per_minute,
+ int total_janks_per_minute) {
+ reports_.emplace_back(
+ janky_intervals_per_minute, total_janks_per_minute);
+ }),
+ OnlyObservedThreadsForTest(true));
+
+ internal::SetBlockingObserverForCurrentThread(&main_thread_observer);
+ }
+
+ void StopMonitoring() {
+ // Reclaim worker threads before CancelMonitoringForTesting() to avoid a
+ // data race (crbug.com/1071166#c16).
+ task_environment_.reset();
+ internal::IOJankMonitoringWindow::CancelMonitoringForTesting();
+ internal::ClearBlockingObserverForCurrentThread();
+ }
+
+ void TearDown() override {
+ if (task_environment_)
+ StopMonitoring();
+ }
+
+ protected:
+ // A member initialized before |task_environment_| that forces worker threads
+ // to be started synchronously. This avoids a tricky race where Linux invokes
+ // SetCurrentThreadType() from early main, before invoking ThreadMain and
+ // yielding control to the thread pool impl. That causes a ScopedBlockingCall
+ // in platform_thread_linux.cc:SetThreadCgroupForThreadType and interferes
+ // with this test. This solution is quite intrusive but is the simplest we can
+ // do for this unique corner case.
+ struct SetSynchronousThreadStart {
+ SetSynchronousThreadStart() {
+ internal::ThreadPoolImpl::SetSynchronousThreadStartForTesting(true);
+ }
+ ~SetSynchronousThreadStart() {
+ internal::ThreadPoolImpl::SetSynchronousThreadStartForTesting(false);
+ }
+ } set_synchronous_thread_start_;
+
+ // The registered lambda above may report to this from any thread. It is
+ // nonetheless safe to read this from the test body as
+ // TaskEnvironment+MOCK_TIME advances the test in lock steps.
+ std::vector<std::pair<int, int>> reports_;
+
+ absl::optional<test::TaskEnvironment> task_environment_;
+
+ // The main thread needs to register a BlockingObserver per
+ // OnlyObservedThreadsForTest(true) but doesn't otherwise care about
+ // observing.
+ testing::NiceMock<MockBlockingObserver> main_thread_observer;
+};
+
+} // namespace
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, Basic) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ // Advance precisely to the end of this window.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow - kJankTiming);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7, 7)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, NestedDoesntMatter) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ ScopedBlockingCall nested(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ // Jump to the next window.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7, 7)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, ManyInAWindow) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ constexpr auto kIdleTiming = Seconds(3);
+
+ for (int i = 0; i < 3; ++i) {
+ {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+ task_environment_->FastForwardBy(kIdleTiming);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ // Complete the current window.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow -
+ (kJankTiming + kIdleTiming) * 3);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7 * 3, 7 * 3)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, OverlappingMultipleWindows) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kMonitoringWindow * 3 +
+ internal::IOJankMonitoringWindow::kIOJankInterval * 5;
+
+ {
+ ScopedBlockingCall blocked_for_3windows(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // Fast-forward by another window with no active blocking calls.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ // 3 windows janky for their full breadth and 1 window janky for 5 seconds.
+ EXPECT_THAT(reports_,
+ ElementsAre(std::make_pair(60, 60), std::make_pair(60, 60),
+ std::make_pair(60, 60), std::make_pair(5, 5)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, InstantUnblockReportsZero) {
+ { ScopedBlockingCall instant_unblock(FROM_HERE, BlockingType::MAY_BLOCK); }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+
+ // No blocking call in next window also reports zero.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+ EXPECT_THAT(reports_,
+ ElementsAre(std::make_pair(0, 0), std::make_pair(0, 0)));
+}
+
+// Start the jank mid-interval; that interval should be counted but the last
+// incomplete interval won't count.
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, Jank7sMidInterval) {
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kIOJankInterval / 3);
+
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7, 7)));
+}
+
+// Start the jank mid-interval; that interval should be counted but the second
+// one won't count.
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, Jank1sMidInterval) {
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kIOJankInterval / 3);
+
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval;
+ {
+ ScopedBlockingCall blocked_for_1s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(1, 1)));
+}
+
+// Jank that lasts for 1.3 intervals should be rounded down to 1.
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, JankRoundDown) {
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kIOJankInterval * 0.9);
+
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 1.3;
+ {
+ ScopedBlockingCall blocked_for_1s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(1, 1)));
+}
+
+// Jank that lasts for 1.7 intervals should be rounded up to 2.
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, JankRoundUp) {
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kIOJankInterval * 0.5);
+
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 1.7;
+ {
+ ScopedBlockingCall blocked_for_1s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(2, 2)));
+}
+
+// Start mid-interval and perform an operation that overlaps into the next one
+// but is under the jank timing.
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, NoJankMidInterval) {
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kIOJankInterval / 3);
+
+ {
+ ScopedBlockingCall non_janky(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kIOJankInterval - Milliseconds(1));
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, MultiThreaded) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+
+ // Every worker needs to block for precise clock management; hence we can't
+ // test beyond the TaskEnvironment's capacity.
+ const int kNumJankyTasks =
+ test::TaskEnvironment::kNumForegroundThreadPoolThreads;
+
+ TestWaitableEvent all_threads_blocked;
+ auto on_thread_blocked = BarrierClosure(
+ kNumJankyTasks,
+ BindOnce(&TestWaitableEvent::Signal, Unretained(&all_threads_blocked)));
+
+ TestWaitableEvent resume_all_threads;
+
+ for (int i = 0; i < kNumJankyTasks; ++i) {
+ base::ThreadPool::PostTask(
+ FROM_HERE, {MayBlock()}, BindLambdaForTesting([&]() {
+ ScopedBlockingCall blocked_until_signal(FROM_HERE,
+ BlockingType::MAY_BLOCK);
+ on_thread_blocked.Run();
+
+ ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
+ resume_all_threads.Wait();
+ }));
+ }
+
+ all_threads_blocked.Wait();
+ task_environment_->AdvanceClock(kJankTiming);
+ resume_all_threads.Signal();
+ task_environment_->RunUntilIdle();
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ // Still only 7 janky internals, but more overall janks.
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7, 7 * kNumJankyTasks)));
+}
+
+// 3 janks of 3 seconds; overlapping but starting 1 second apart from each
+// other.
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, MultiThreadedOverlapped) {
+ static const int kNumJankyTasks = 3;
+ static_assert(
+ kNumJankyTasks <= test::TaskEnvironment::kNumForegroundThreadPoolThreads,
+ "");
+
+ TestWaitableEvent next_task_is_blocked(WaitableEvent::ResetPolicy::AUTOMATIC);
+
+ TestWaitableEvent resume_thread[kNumJankyTasks] = {};
+ TestWaitableEvent exited_blocking_scope[kNumJankyTasks] = {};
+
+ auto blocking_task = BindLambdaForTesting([&](int task_index) {
+ {
+ // Simulate jank until |resume_thread[task_index]| is signaled.
+ ScopedBlockingCall blocked_until_signal(FROM_HERE,
+ BlockingType::MAY_BLOCK);
+ next_task_is_blocked.Signal();
+
+ ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
+ resume_thread[task_index].Wait();
+ }
+ exited_blocking_scope[task_index].Signal();
+ });
+
+ // [0-1]s
+ base::ThreadPool::PostTask(FROM_HERE, {MayBlock()},
+ BindOnce(blocking_task, 0));
+ next_task_is_blocked.Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kIOJankInterval);
+
+ // [1-2]s
+ base::ThreadPool::PostTask(FROM_HERE, {MayBlock()},
+ BindOnce(blocking_task, 1));
+ next_task_is_blocked.Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kIOJankInterval);
+
+ // [2-3]s
+ base::ThreadPool::PostTask(FROM_HERE, {MayBlock()},
+ BindOnce(blocking_task, 2));
+ next_task_is_blocked.Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kIOJankInterval);
+
+ // [3-6]s
+ for (int i = 0; i < kNumJankyTasks; ++i) {
+ resume_thread[i].Signal();
+ exited_blocking_scope[i].Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kIOJankInterval);
+ }
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ // 9s of total janks spread across 5 intervals.
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(5, 9)));
+}
+
+// 3 janks of 180 seconds; overlapping but starting 60s apart from each other.
+// First one starting at 10 seconds (can't start later than that or we'll trip
+// the kTimeDiscrepancyTimeout per TaskEnvironment's inability to RunUntilIdle()
+// with pending blocked tasks).
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, MultiThreadedOverlappedWindows) {
+ constexpr int kNumJankyTasks = 3;
+ static_assert(
+ kNumJankyTasks <= test::TaskEnvironment::kNumForegroundThreadPoolThreads,
+ "");
+
+ TestWaitableEvent next_task_is_blocked(WaitableEvent::ResetPolicy::AUTOMATIC);
+
+ TestWaitableEvent resume_thread[kNumJankyTasks] = {};
+ TestWaitableEvent exited_blocking_scope[kNumJankyTasks] = {};
+
+ auto blocking_task = BindLambdaForTesting([&](int task_index) {
+ {
+ // Simulate jank until |resume_thread[task_index]| is signaled.
+ ScopedBlockingCall blocked_until_signal(FROM_HERE,
+ BlockingType::MAY_BLOCK);
+ next_task_is_blocked.Signal();
+
+ ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
+ resume_thread[task_index].Wait();
+ }
+ exited_blocking_scope[task_index].Signal();
+ });
+
+ // [0-10s] (minus 1 ms to avoid reaching the timeout; this also tests the
+ // logic that intervals are rounded down to the starting interval (e.g.
+ // interval 9/60 in this case)).
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kTimeDiscrepancyTimeout -
+ Milliseconds(1));
+
+ // [10-70]s
+ base::ThreadPool::PostTask(FROM_HERE, {MayBlock()},
+ BindOnce(blocking_task, 0));
+ next_task_is_blocked.Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ // [70-130]s
+ base::ThreadPool::PostTask(FROM_HERE, {MayBlock()},
+ BindOnce(blocking_task, 1));
+ next_task_is_blocked.Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ // [130-190]s
+ base::ThreadPool::PostTask(FROM_HERE, {MayBlock()},
+ BindOnce(blocking_task, 2));
+ next_task_is_blocked.Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ // [190-370]s
+ for (int i = 0; i < kNumJankyTasks; ++i) {
+ resume_thread[i].Signal();
+ exited_blocking_scope[i].Wait();
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+ }
+
+ // Already past the last window (relevant events end at 360s); flush the
+ // pending ripe delayed task that will complete the last window.
+ task_environment_->RunUntilIdle();
+
+ // 540s(180s*3) of total janks spread across 300 intervals in 6 windows.
+ // Distributed as such (zoomed out to 6 intervals per window):
+ // [011111]
+ // [122222]
+ // [233333]
+ // [322222]
+ // [21111]
+ // [100000]
+ // Starting at the 9th interval per the 10s-1ms offset start.
+ EXPECT_THAT(reports_,
+ ElementsAre(std::make_pair(51, 51), std::make_pair(60, 111),
+ std::make_pair(60, 171), std::make_pair(60, 129),
+ std::make_pair(60, 69), std::make_pair(9, 9)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, CancellationAcrossSleep) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kJankTiming);
+ }
+
+ // Jump just beyond the kTimeDiscrepancyTimeout for the next window.
+ task_environment_->AdvanceClock(
+ internal::IOJankMonitoringWindow::kMonitoringWindow +
+ internal::IOJankMonitoringWindow::kTimeDiscrepancyTimeout - kJankTiming);
+ task_environment_->RunUntilIdle();
+
+ // Window was canceled and previous jank was not reported.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ // The second window should be independent and need a full kMonitoringWindow
+ // to elapse before reporting.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow - Seconds(1));
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(Seconds(1));
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, SleepWithLongJank) {
+ {
+ ScopedBlockingCall blocked_through_sleep(FROM_HERE,
+ BlockingType::MAY_BLOCK);
+
+ // Fast-forward 2 full windows and almost to the end of the 3rd.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow * 3 - Seconds(1));
+
+ // Simulate a "sleep" over the timeout threshold.
+ task_environment_->AdvanceClock(
+ Seconds(1) + internal::IOJankMonitoringWindow::kTimeDiscrepancyTimeout);
+ }
+
+ // Two full jank windows are reported when the ScopedBlokcingCall unwinds but
+ // the 3rd is canceled.
+ EXPECT_THAT(reports_,
+ ElementsAre(std::make_pair(60, 60), std::make_pair(60, 60)));
+
+ // The 4th window has a new |start_time| so completing the "remaining delta"
+ // doesn't cause a report from the cancelled 3rd window.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow - Seconds(1));
+ EXPECT_THAT(reports_,
+ ElementsAre(std::make_pair(60, 60), std::make_pair(60, 60)));
+
+ // Completing the whole 4th window generates a report.
+ task_environment_->FastForwardBy(Seconds(1));
+ EXPECT_THAT(reports_,
+ ElementsAre(std::make_pair(60, 60), std::make_pair(60, 60),
+ std::make_pair(0, 0)));
+}
+
+// Verifies that blocking calls on background workers aren't monitored.
+// Platforms where !CanUseBackgroundThreadTypeForWorkerThread() will still
+// monitor this jank (as it may interfere with other foreground work).
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, BackgroundBlockingCallsIgnored) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+
+ TestWaitableEvent task_running;
+ TestWaitableEvent resume_task;
+
+ base::ThreadPool::PostTask(
+ FROM_HERE, {TaskPriority::BEST_EFFORT, MayBlock()},
+ BindLambdaForTesting([&]() {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_running.Signal();
+
+ ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
+ resume_task.Wait();
+ }));
+
+ task_running.Wait();
+ task_environment_->AdvanceClock(kJankTiming);
+ resume_task.Signal();
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ if (internal::CanUseBackgroundThreadTypeForWorkerThread())
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+ else
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7, 7)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest,
+ BackgroundAndForegroundCallsMixed) {
+ constexpr auto kJankTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+
+ TestWaitableEvent tasks_running;
+ auto on_task_running = BarrierClosure(
+ 2, BindOnce(&TestWaitableEvent::Signal, Unretained(&tasks_running)));
+ TestWaitableEvent resume_tasks;
+
+ base::ThreadPool::PostTask(
+ FROM_HERE, {TaskPriority::BEST_EFFORT, MayBlock()},
+ BindLambdaForTesting([&]() {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ on_task_running.Run();
+
+ ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
+ resume_tasks.Wait();
+ }));
+
+ base::ThreadPool::PostTask(
+ FROM_HERE, {TaskPriority::USER_BLOCKING, MayBlock()},
+ BindLambdaForTesting([&]() {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ on_task_running.Run();
+
+ ScopedAllowBaseSyncPrimitivesForTesting allow_wait;
+ resume_tasks.Wait();
+ }));
+
+ tasks_running.Wait();
+ task_environment_->AdvanceClock(kJankTiming);
+ resume_tasks.Signal();
+
+ // No janks reported before the monitoring window completes.
+ EXPECT_THAT(reports_, ElementsAre());
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ if (internal::CanUseBackgroundThreadTypeForWorkerThread())
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7, 7)));
+ else
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(7, 14)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, WillBlockNotMonitored) {
+ constexpr auto kBlockedTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_7s(FROM_HERE, BlockingType::WILL_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ }
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest,
+ NestedWillBlockCancelsMonitoring) {
+ constexpr auto kBlockedTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_14s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ ScopedBlockingCall will_block_for_7s(FROM_HERE, BlockingType::WILL_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ }
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, NestedMayBlockIgnored) {
+ constexpr auto kBlockedTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_14s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ ScopedBlockingCall may_block_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ }
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(14, 14)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest, BaseSyncPrimitivesNotMonitored) {
+ constexpr auto kBlockedTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ // Even with MAY_BLOCK; base-sync-primitives aren't considered I/O jank
+ // (base-sync-primitives induced janks/hangs are captured by other tools,
+ // like Slow Reports and HangWatcher).
+ internal::ScopedBlockingCallWithBaseSyncPrimitives
+ base_sync_primitives_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ }
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+}
+
+TEST_F(ScopedBlockingCallIOJankMonitoringTest,
+ NestedBaseSyncPrimitivesCancels) {
+ constexpr auto kBlockedTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * 7;
+ {
+ ScopedBlockingCall blocked_for_14s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ internal::ScopedBlockingCallWithBaseSyncPrimitives
+ base_sync_primitives_for_7s(FROM_HERE, BlockingType::MAY_BLOCK);
+ task_environment_->FastForwardBy(kBlockedTiming);
+ }
+
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow);
+
+ EXPECT_THAT(reports_, ElementsAre(std::make_pair(0, 0)));
+}
+
+// Regression test for crbug.com/1209622
+TEST_F(ScopedBlockingCallIOJankMonitoringTest,
+ RacySampleNearMonitoringWindowBoundary) {
+ constexpr auto kDeltaFromBoundary = Milliseconds(1);
+ const int kNumBlockedIntervals = 7;
+ constexpr auto kBlockedTiming =
+ internal::IOJankMonitoringWindow::kIOJankInterval * kNumBlockedIntervals;
+ // kBlockedTiming must be below kTimeDiscrepancyTimeout or racing worker
+ // threads might cancel the next window when ~ScopedBlockingCall lands too far
+ // in the future (since AdvanceClock() doesn't cause delayed tasks to run and
+ // the first window to expire when expected).
+ static_assert(kBlockedTiming <=
+ internal::IOJankMonitoringWindow::kTimeDiscrepancyTimeout,
+ "");
+
+ // Start this test near an IOJankMonitoringWindow boundary.
+ task_environment_->FastForwardBy(
+ internal::IOJankMonitoringWindow::kMonitoringWindow - kDeltaFromBoundary);
+
+ const int kNumRacingThreads =
+ test::TaskEnvironment::kNumForegroundThreadPoolThreads;
+
+ TestWaitableEvent all_threads_blocked;
+ auto on_thread_blocked = BarrierClosure(
+ kNumRacingThreads,
+ BindOnce(&TestWaitableEvent::Signal, Unretained(&all_threads_blocked)));
+ TestWaitableEvent unblock_worker_threads;
+
+ // First warmup the ThreadPool so there are kNumRacingThreads ready threads
+ // (to maximize the likelihood of a race).
+ for (int i = 0; i < kNumRacingThreads; ++i) {
+ ThreadPool::PostTask(FROM_HERE, {MayBlock()}, BindLambdaForTesting([&]() {
+ on_thread_blocked.Run();
+ unblock_worker_threads.Wait();
+ }));
+ }
+ all_threads_blocked.Wait();
+ unblock_worker_threads.Signal();
+ task_environment_->RunUntilIdle();
+
+ all_threads_blocked.Reset();
+ on_thread_blocked = BarrierClosure(
+ kNumRacingThreads,
+ BindOnce(&TestWaitableEvent::Signal, Unretained(&all_threads_blocked)));
+ unblock_worker_threads.Reset();
+
+ for (int i = 0; i < kNumRacingThreads; ++i) {
+ ThreadPool::PostTask(FROM_HERE, {MayBlock()}, BindLambdaForTesting([&]() {
+ ScopedBlockingCall blocked_for_14s(
+ FROM_HERE, BlockingType::MAY_BLOCK);
+ on_thread_blocked.Run();
+ unblock_worker_threads.Wait();
+ }));
+ }
+
+ // Race the worker threads sampling Now() at the start of their blocking call
+ // to reproduce the conditions of crbug.com/1209622. The race occurs if a
+ // worker thread samples Now() before it moves across the boundary but then
+ // the boundary is crossed before it sampled its assigned
+ // IOJankMonitoringWindow, getting a window which doesn't overlap with the
+ // sampled Now() identifying the ScopedBlockingCall's entry point.
+ task_environment_->AdvanceClock(kDeltaFromBoundary);
+ {
+ // We have to use AdvanceClock() above as a FastForwardBy() would stall on
+ // the blocked workers. This means the delayed task causing the first
+ // IOJankMonitoringWindow to expire didn't run. Entering a new
+ // ScopedBlockingCall forces this to happen.
+ ScopedBlockingCall trigger_window(FROM_HERE, BlockingType::MAY_BLOCK);
+ }
+
+ all_threads_blocked.Wait();
+ task_environment_->AdvanceClock(kBlockedTiming);
+ // If a worker thread holds a "begin" timestamp in the past versus its
+ // assigned IOJankMonitoringWindow, completing the janky ScopedBlockingCall
+ // will result in an OOB-index into
+ // |IOJankMonitoringWindow::intervals_jank_count_|.
+ unblock_worker_threads.Signal();
+ task_environment_->RunUntilIdle();
+
+ // Force a report immediately.
+ StopMonitoring();
+
+ // Test covered 2 monitoring windows.
+ ASSERT_EQ(reports_.size(), 2U);
+
+ // Between 0 and kNumRacingThreads sampled Now() and their
+ // IOJankMonitoringWindow before Now() was fast-forwarded by
+ // kDeltaFromBoundary.
+ auto [janky_intervals_count, total_jank_count] = reports_[0];
+ EXPECT_GE(janky_intervals_count, 0);
+ EXPECT_LE(janky_intervals_count, 1);
+ EXPECT_GE(total_jank_count, 0);
+ EXPECT_LE(total_jank_count, kNumRacingThreads);
+ std::tie(janky_intervals_count, total_jank_count) = reports_[1];
+ EXPECT_GE(janky_intervals_count, kNumBlockedIntervals - 1);
+ EXPECT_LE(janky_intervals_count, kNumBlockedIntervals);
+ EXPECT_GE(total_jank_count, (kNumBlockedIntervals - 1) * kNumRacingThreads);
+ EXPECT_LE(total_jank_count, kNumBlockedIntervals * kNumRacingThreads);
+}
+
} // namespace base
diff --git a/base/threading/scoped_thread_priority.cc b/base/threading/scoped_thread_priority.cc
new file mode 100644
index 0000000..53922fb
--- /dev/null
+++ b/base/threading/scoped_thread_priority.cc
@@ -0,0 +1,85 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/scoped_thread_priority.h"
+
+#include "base/location.h"
+#include "base/threading/platform_thread.h"
+#include "base/trace_event/base_tracing.h"
+#include "build/build_config.h"
+
+namespace base {
+
+ScopedBoostPriority::ScopedBoostPriority(ThreadType target_thread_type) {
+ DCHECK_LT(target_thread_type, ThreadType::kRealtimeAudio);
+ const ThreadType original_thread_type =
+ PlatformThread::GetCurrentThreadType();
+ const bool should_boost = original_thread_type < target_thread_type &&
+ PlatformThread::CanChangeThreadType(
+ original_thread_type, target_thread_type) &&
+ PlatformThread::CanChangeThreadType(
+ target_thread_type, original_thread_type);
+ if (should_boost) {
+ original_thread_type_.emplace(original_thread_type);
+ PlatformThread::SetCurrentThreadType(target_thread_type);
+ }
+}
+
+ScopedBoostPriority::~ScopedBoostPriority() {
+ if (original_thread_type_.has_value())
+ PlatformThread::SetCurrentThreadType(original_thread_type_.value());
+}
+
+namespace internal {
+
+ScopedMayLoadLibraryAtBackgroundPriority::
+ ScopedMayLoadLibraryAtBackgroundPriority(const Location& from_here,
+ std::atomic_bool* already_loaded)
+#if BUILDFLAG(IS_WIN)
+ : already_loaded_(already_loaded)
+#endif // BUILDFLAG(IS_WIN)
+{
+ TRACE_EVENT_BEGIN(
+ "base", "ScopedMayLoadLibraryAtBackgroundPriority",
+ [&](perfetto::EventContext ctx) {
+ ctx.event()->set_source_location_iid(
+ base::trace_event::InternedSourceLocation::Get(&ctx, from_here));
+ });
+
+#if BUILDFLAG(IS_WIN)
+ if (already_loaded_ && already_loaded_->load(std::memory_order_relaxed))
+ return;
+
+ const base::ThreadType thread_type = PlatformThread::GetCurrentThreadType();
+ if (thread_type == base::ThreadType::kBackground) {
+ original_thread_type_ = thread_type;
+ PlatformThread::SetCurrentThreadType(base::ThreadType::kDefault);
+
+ TRACE_EVENT_BEGIN0(
+ "base",
+ "ScopedMayLoadLibraryAtBackgroundPriority : Priority Increased");
+ }
+#endif // BUILDFLAG(IS_WIN)
+}
+
+ScopedMayLoadLibraryAtBackgroundPriority::
+ ~ScopedMayLoadLibraryAtBackgroundPriority() {
+ // Trace events must be closed in reverse order of opening so that they nest
+ // correctly.
+#if BUILDFLAG(IS_WIN)
+ if (original_thread_type_) {
+ TRACE_EVENT_END0(
+ "base",
+ "ScopedMayLoadLibraryAtBackgroundPriority : Priority Increased");
+ PlatformThread::SetCurrentThreadType(original_thread_type_.value());
+ }
+
+ if (already_loaded_)
+ already_loaded_->store(true, std::memory_order_relaxed);
+#endif // BUILDFLAG(IS_WIN)
+ TRACE_EVENT_END0("base", "ScopedMayLoadLibraryAtBackgroundPriority");
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/threading/scoped_thread_priority.h b/base/threading/scoped_thread_priority.h
new file mode 100644
index 0000000..7b29938
--- /dev/null
+++ b/base/threading/scoped_thread_priority.h
@@ -0,0 +1,109 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_SCOPED_THREAD_PRIORITY_H_
+#define BASE_THREADING_SCOPED_THREAD_PRIORITY_H_
+
+#include <atomic>
+
+#include "base/base_export.h"
+#include "base/compiler_specific.h"
+#include "base/location.h"
+#include "base/memory/raw_ptr.h"
+#include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace base {
+
+class Location;
+enum class ThreadType : int;
+
+// INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE(name) produces an identifier by
+// appending the current line number to |name|. This is used to avoid name
+// collisions from variables defined inside a macro.
+#define INTERNAL_SCOPED_THREAD_PRIORITY_CONCAT(a, b) a##b
+// CONCAT1 provides extra level of indirection so that __LINE__ macro expands.
+#define INTERNAL_SCOPED_THREAD_PRIORITY_CONCAT1(a, b) \
+ INTERNAL_SCOPED_THREAD_PRIORITY_CONCAT(a, b)
+#define INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE(name) \
+ INTERNAL_SCOPED_THREAD_PRIORITY_CONCAT1(name, __LINE__)
+
+// All code that may load a DLL on a background thread must be surrounded by a
+// scope that starts with this macro.
+//
+// Example:
+// Foo();
+// {
+// SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+// LoadMyDll();
+// }
+// Bar();
+//
+// The macro raises the thread priority to NORMAL for the scope if no other
+// thread has completed the current scope already (multiple threads can racily
+// begin the initialization and will all be boosted for it). On Windows, loading
+// a DLL on a background thread can lead to a priority inversion on the loader
+// lock and cause huge janks.
+#define SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY() \
+ static std::atomic_bool INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
+ already_loaded){false}; \
+ base::internal::ScopedMayLoadLibraryAtBackgroundPriority \
+ INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
+ scoped_may_load_library_at_background_priority)( \
+ FROM_HERE, \
+ &INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE(already_loaded));
+
+// Like SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY, but raises the thread
+// priority every time the scope is entered. Use this around code that may
+// conditionally load a DLL each time it is executed, or which repeatedly loads
+// and unloads DLLs.
+#define SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY() \
+ base::internal::ScopedMayLoadLibraryAtBackgroundPriority \
+ INTERNAL_SCOPED_THREAD_PRIORITY_APPEND_LINE( \
+ scoped_may_load_library_at_background_priority)(FROM_HERE, nullptr);
+
+// Boosts the current thread's priority to match the priority of threads of
+// |target_thread_type| in this scope.
+class BASE_EXPORT ScopedBoostPriority {
+ public:
+ explicit ScopedBoostPriority(ThreadType target_thread_type);
+ ~ScopedBoostPriority();
+
+ ScopedBoostPriority(const ScopedBoostPriority&) = delete;
+ ScopedBoostPriority& operator=(const ScopedBoostPriority&) = delete;
+
+ private:
+ absl::optional<ThreadType> original_thread_type_;
+};
+
+namespace internal {
+
+class BASE_EXPORT ScopedMayLoadLibraryAtBackgroundPriority {
+ public:
+ // Boosts thread priority to NORMAL within its scope if |already_loaded| is
+ // nullptr or set to false.
+ explicit ScopedMayLoadLibraryAtBackgroundPriority(
+ const Location& from_here,
+ std::atomic_bool* already_loaded);
+
+ ScopedMayLoadLibraryAtBackgroundPriority(
+ const ScopedMayLoadLibraryAtBackgroundPriority&) = delete;
+ ScopedMayLoadLibraryAtBackgroundPriority& operator=(
+ const ScopedMayLoadLibraryAtBackgroundPriority&) = delete;
+
+ ~ScopedMayLoadLibraryAtBackgroundPriority();
+
+ private:
+#if BUILDFLAG(IS_WIN)
+ // The original priority when invoking entering the scope().
+ absl::optional<ThreadType> original_thread_type_;
+ const raw_ptr<std::atomic_bool> already_loaded_;
+#endif
+};
+
+} // namespace internal
+
+} // namespace base
+
+#endif // BASE_THREADING_SCOPED_THREAD_PRIORITY_H_
diff --git a/base/threading/scoped_thread_priority_unittest.cc b/base/threading/scoped_thread_priority_unittest.cc
new file mode 100644
index 0000000..b44c1a3
--- /dev/null
+++ b/base/threading/scoped_thread_priority_unittest.cc
@@ -0,0 +1,180 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/scoped_thread_priority.h"
+
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+// Tests in this file invoke an API that tracks state in static variable. They
+// can therefore only be invoked once per process.
+#define ASSERT_RUNS_ONCE() \
+ static int num_times_run = 0; \
+ ++num_times_run; \
+ if (num_times_run > 1) \
+ ADD_FAILURE() << "This test cannot run multiple times in the same " \
+ "process.";
+
+static ThreadType kAllThreadTypes[] = {
+ ThreadType::kRealtimeAudio, ThreadType::kDisplayCritical,
+ ThreadType::kCompositing, ThreadType::kDefault, ThreadType::kBackground};
+
+static_assert(static_cast<int>(ThreadType::kBackground) == 0,
+ "kBackground isn't lowest");
+static_assert(ThreadType::kRealtimeAudio == ThreadType::kMaxValue,
+ "kRealtimeAudio isn't highest");
+
+class ScopedThreadPriorityTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ // Ensures the default thread priority is set.
+ ASSERT_EQ(ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+ }
+};
+
+#if BUILDFLAG(IS_WIN)
+void FunctionThatBoostsPriorityOnFirstInvoke(
+ ThreadPriorityForTest expected_priority) {
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+ EXPECT_EQ(expected_priority,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+}
+
+void FunctionThatBoostsPriorityOnEveryInvoke() {
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY();
+ EXPECT_EQ(base::ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+}
+
+#endif // BUILDFLAG(IS_WIN)
+
+} // namespace
+
+TEST_F(ScopedThreadPriorityTest, BasicTest) {
+ for (auto from : kAllThreadTypes) {
+ if (!PlatformThread::CanChangeThreadType(ThreadType::kDefault, from))
+ continue;
+ for (auto to : kAllThreadTypes) {
+ // ThreadType::kRealtimeAudio is not a valid |target_thread_type| for
+ // ScopedBoostPriority.
+ if (to == ThreadType::kRealtimeAudio)
+ continue;
+ Thread thread("ScopedThreadPriorityTest");
+ thread.StartWithOptions(Thread::Options(from));
+ thread.WaitUntilThreadStarted();
+ thread.task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](ThreadType from, ThreadType to) {
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(), from);
+ {
+ ScopedBoostPriority scoped_boost_priority(to);
+ bool will_boost_priority =
+ from < to &&
+ PlatformThread::CanChangeThreadType(from, to) &&
+ PlatformThread::CanChangeThreadType(to, from);
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(),
+ will_boost_priority ? to : from);
+ }
+ EXPECT_EQ(PlatformThread::GetCurrentThreadType(), from);
+ },
+ from, to));
+ }
+ }
+}
+
+TEST_F(ScopedThreadPriorityTest, WithoutPriorityBoost) {
+ ASSERT_RUNS_ONCE();
+
+ // Validates that a thread at normal priority keep the same priority.
+ {
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+ EXPECT_EQ(ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+ }
+ EXPECT_EQ(ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+}
+
+#if BUILDFLAG(IS_WIN)
+TEST_F(ScopedThreadPriorityTest, WithPriorityBoost) {
+ ASSERT_RUNS_ONCE();
+
+ // Validates that a thread at background priority is boosted to normal
+ // priority.
+ PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
+ {
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+ EXPECT_EQ(ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+ }
+ EXPECT_EQ(ThreadPriorityForTest::kBackground,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+
+ // Put back the default thread priority.
+ PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
+}
+#endif // BUILDFLAG(IS_WIN)
+
+#if BUILDFLAG(IS_WIN)
+TEST_F(ScopedThreadPriorityTest, NestedScope) {
+ ASSERT_RUNS_ONCE();
+
+ PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
+
+ {
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+ EXPECT_EQ(ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+ {
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+ EXPECT_EQ(ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+ }
+ EXPECT_EQ(ThreadPriorityForTest::kNormal,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+ }
+
+ EXPECT_EQ(ThreadPriorityForTest::kBackground,
+ PlatformThread::GetCurrentThreadPriorityForTest());
+
+ // Put back the default thread priority.
+ PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
+}
+#endif // BUILDFLAG(IS_WIN)
+
+#if BUILDFLAG(IS_WIN)
+TEST_F(ScopedThreadPriorityTest, FunctionThatBoostsPriorityOnFirstInvoke) {
+ ASSERT_RUNS_ONCE();
+
+ PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
+
+ FunctionThatBoostsPriorityOnFirstInvoke(base::ThreadPriorityForTest::kNormal);
+ FunctionThatBoostsPriorityOnFirstInvoke(
+ base::ThreadPriorityForTest::kBackground);
+
+ // Put back the default thread priority.
+ PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
+}
+
+TEST_F(ScopedThreadPriorityTest, FunctionThatBoostsPriorityOnEveryInvoke) {
+ PlatformThread::SetCurrentThreadType(ThreadType::kBackground);
+
+ FunctionThatBoostsPriorityOnEveryInvoke();
+ FunctionThatBoostsPriorityOnEveryInvoke();
+
+ // Put back the default thread priority.
+ PlatformThread::SetCurrentThreadType(ThreadType::kDefault);
+}
+
+#endif // BUILDFLAG(IS_WIN)
+
+} // namespace base
diff --git a/base/threading/sequence_bound.h b/base/threading/sequence_bound.h
index b4bd183..02380d7 100644
--- a/base/threading/sequence_bound.h
+++ b/base/threading/sequence_bound.h
@@ -1,4 +1,4 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,247 +6,700 @@
#define BASE_THREADING_SEQUENCE_BOUND_H_
#include <new>
+#include <tuple>
#include <type_traits>
+#include <utility>
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/compiler_specific.h"
+#include "base/check.h"
#include "base/location.h"
-#include "base/memory/aligned_memory.h"
-#include "base/memory/ptr_util.h"
-#include "base/sequenced_task_runner.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/run_loop.h"
+#include "base/sequence_checker.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/threading/sequence_bound_internal.h"
namespace base {
-// SequenceBound facilitates owning objects that live on a specified sequence,
-// which is potentially different than the owner's sequence. It encapsulates
-// the work of posting tasks to the specified sequence to construct T, call
-// methods on T, and destroy T.
+// Performing blocking work on a different task runner is a common pattern for
+// improving responsiveness of foreground task runners. `SequenceBound<T>`
+// provides an abstraction for an owner object living on the owner sequence, to
+// construct, call methods on, and destroy an object of type T that lives on a
+// different sequence (the bound sequence).
//
-// It does not provide explicit access to the underlying object directly, to
-// prevent accidentally using it from the wrong sequence.
+// This makes it natural for code running on different sequences to be
+// partitioned along class boundaries, e.g.:
//
-// Like std::unique_ptr<T>, a SequenceBound<T> may be moved between owners,
-// and posted across threads. It may also be up-casted (only), to permit
-// SequenceBound to be used with interfaces.
+// class Tab {
+// private:
+// void OnScroll() {
+// // ...
+// io_helper_.AsyncCall(&IOHelper::SaveScrollPosition);
+// }
+// base::SequenceBound<IOHelper> io_helper_{GetBackgroundTaskRunner()};
+// };
//
-// Basic usage looks like this:
+// Note: `SequenceBound<T>` intentionally does not expose a raw pointer to the
+// managed `T` to ensure its internal sequence-safety invariants are not
+// violated. As a result, `AsyncCall()` cannot simply use `base::OnceCallback`
//
-// // Some class that lives on |main_task_runner|.
-// class MyClass {
+// SequenceBound also supports replies:
+//
+// class Database {
// public:
-// explicit MyClass(const char* widget_title) {}
-// virtual ~MyClass() { ... }
-// virtual void DoSomething(int arg) { ... }
+// int Query(int value) {
+// return value * value;
+// }
// };
//
-// // On any thread...
-// scoped_refptr<SequencedTaskRunner> main_task_runner = ...;
-// auto widget = SequenceBound<MyClass>(main_task_runner, "My Title");
-// widget.Post(&MyObject::DoSomething, 1234);
+// // SequenceBound itself is owned on
+// // `SequencedTaskRunner::GetCurrentDefault()`. The managed Database
+// // instance managed by it is constructed and owned on `GetDBTaskRunner()`.
+// base::SequenceBound<Database> db(GetDBTaskRunner());
//
-// Note that |widget| is constructed asynchronously on |main_task_runner|,
-// but calling Post() immediately is safe, since the actual call is posted
-// to |main_task_runner| as well.
-//
-// |widget| will be deleted on |main_task_runner| asynchronously when it goes
-// out of scope, or when Reset() is called.
-//
-// Here is a more complicated example that shows injection and upcasting:
-//
-// // Some unrelated class that uses a |MyClass| to do something.
-// class SomeConsumer {
-// public:
-// // Note that ownership of |widget| is given to us!
-// explicit SomeConsumer(SequenceBound<MyClass> widget)
-// : widget_(std::move(widget)) { ... }
-//
-// ~SomeConsumer() {
-// // |widget_| will be destroyed on the associated task runner.
-// }
-//
-// SequenceBound<MyClass> widget_;
+// // `Database::Query()` runs on `GetDBTaskRunner()`, but
+// // `reply_callback` will run on the owner task runner.
+// auto reply_callback = [] (int result) {
+// LOG(ERROR) << result; // Prints 25.
// };
+// db.AsyncCall(&Database::Query).WithArgs(5)
+// .Then(base::BindOnce(reply_callback));
//
-// // Implementation of MyClass.
-// class MyDerivedClass : public MyClass { ... };
+// // When `db` goes out of scope, the Database instance will also be
+// // destroyed via a task posted to `GetDBTaskRunner()`.
//
-// auto widget =
-// SequenceBound<MyDerivedClass>(main_task_runner, ctor args);
-// auto c = new SomeConsumer(std::move(widget)); // upcasts to MyClass
-
-namespace internal {
-
-// If we can't cast |Base*| into |Derived*|, then it's a virtual base if and
-// only if |Base| is actually a base class of |Derived|. Otherwise (including
-// unrelated types), it isn't. We default to Derived* so that the
-// specialization below will apply when the cast to |Derived*| is valid.
-template <typename Base, typename Derived, typename = Derived*>
-struct is_virtual_base_of : public std::is_base_of<Base, Derived> {};
-
-// If we can cast |Base*| into |Derived*|, then it's definitely not a virtual
-// base. When this happens, we'll match the default third template argument.
-template <typename Base, typename Derived>
-struct is_virtual_base_of<Base,
- Derived,
- decltype(static_cast<Derived*>(
- static_cast<Base*>(nullptr)))> : std::false_type {
-};
-
-}; // namespace internal
-
-template <typename T>
+// Sequence safety:
+//
+// Const-qualified methods may be used concurrently from multiple sequences,
+// e.g. `AsyncCall()` or `is_null()`. Calls that are forwarded to the
+// managed `T` will be posted to the bound sequence and executed serially
+// there.
+//
+// Mutable methods (e.g. `Reset()`, destruction, or move assignment) require
+// external synchronization if used concurrently with any other methods,
+// including const-qualified methods.
+//
+// Advanced usage:
+//
+// Using `SequenceBound<std::unique_ptr<T>>` allows transferring ownership of an
+// already-constructed `T` to `SequenceBound`. This can be helpful for more
+// complex situations, where `T` needs to be constructed on a specific sequence
+// that is different from where `T` will ultimately live.
+//
+// Construction (via the constructor or emplace) takes a `std::unique_ptr<T>`
+// instead of forwarding the arguments to `T`'s constructor:
+//
+// std::unique_ptr<Database> db_impl = MakeDatabaseOnMainThread();
+// base::SequenceBound<std::unique_ptr<Database>> db(GetDbTaskRunner(),
+// std::move(db_impl));
+//
+// All other usage (e.g. `AsyncCall()`, `Reset()`) functions identically to a
+// regular `SequenceBound<T>`:
+//
+// // No need to dereference the `std::unique_ptr` explicitly:
+// db.AsyncCall(&Database::Query).WithArgs(5).Then(base::BindOnce(...));
+template <typename T,
+ typename CrossThreadTraits =
+ sequence_bound_internal::CrossThreadTraits>
class SequenceBound {
+ private:
+ using Storage = sequence_bound_internal::Storage<T, CrossThreadTraits>;
+ // This is usually just `T` except if `T` is a `std::unique_ptr`; in that
+ // case, `UnwrappedT` is the type of the object owned by the
+ // `std::unique_ptr`, e.g. if `T` is `std::unique_ptr<std::string>`, then
+ // UnwrappedT is `std::string`.
+ using UnwrappedT = std::remove_pointer_t<typename Storage::Ptr>;
+
public:
- // Allow explicit null.
+ template <typename Signature>
+ using CrossThreadTask =
+ typename CrossThreadTraits::template CrossThreadTask<Signature>;
+
+ // Note: on construction, SequenceBound binds to the current sequence. Any
+ // subsequent SequenceBound calls (including destruction) must run on that
+ // same sequence.
+
+ // Constructs a null SequenceBound with no managed `T`.
SequenceBound() = default;
- // Construct a new instance of |T| that will be accessed only on
- // |task_runner|. One may post calls to it immediately upon return.
- // This is marked as NO_SANITIZE because cfi doesn't like that we're casting
- // uninitialized memory to a |T*|. However, it's safe since (a) the cast is
- // defined (see http://eel.is/c++draft/basic.life#6 for details), and (b) we
- // don't use the resulting pointer in any way that requries it to be
- // constructed, except by posting such a access to |impl_task_runner_| after
- // posting construction there as well.
+ // Constructs a SequenceBound that manages a new instance of `T` on
+ // `task_runner`. `T` will be constructed on `task_runner`.
+ //
+ // Once this constructor returns, it is safe to immediately use `AsyncCall()`,
+ // et cetera; these calls will be sequenced after the construction of the
+ // managed `T`.
template <typename... Args>
- SequenceBound(scoped_refptr<base::SequencedTaskRunner> task_runner,
- Args&&... args) NO_SANITIZE("cfi-unrelated-cast")
+ explicit SequenceBound(scoped_refptr<SequencedTaskRunner> task_runner,
+ Args&&... args)
: impl_task_runner_(std::move(task_runner)) {
- // Allocate space for but do not construct an instance of |T|.
- storage_ = AlignedAlloc(sizeof(T), alignof(T));
- t_ = reinterpret_cast<T*>(storage_);
-
- // Post construction to the impl thread.
- impl_task_runner_->PostTask(
- FROM_HERE,
- base::BindOnce(&ConstructOwnerRecord<Args...>, base::Unretained(t_),
- std::forward<Args>(args)...));
+ storage_.Construct(*impl_task_runner_, std::forward<Args>(args)...);
}
+ // If non-null, the managed `T` will be destroyed on `impl_task_runner_`.`
~SequenceBound() { Reset(); }
- // Move construction from the same type can just take the pointer without
- // adjusting anything. This is required in addition to the move conversion
- // constructor below.
+ // Disallow copy or assignment. SequenceBound has single ownership of the
+ // managed `T`.
+ SequenceBound(const SequenceBound&) = delete;
+ SequenceBound& operator=(const SequenceBound&) = delete;
+
+ // Move construction and assignment.
SequenceBound(SequenceBound&& other) { MoveRecordFrom(other); }
- // Move construction is supported from any type that's compatible with |T|.
- // This case handles |From| != |T|, so we must adjust the pointer offset.
- template <typename From>
- SequenceBound(SequenceBound<From>&& other) {
- MoveRecordFrom(other);
- }
-
SequenceBound& operator=(SequenceBound&& other) {
- // Clean up any object we currently own.
Reset();
MoveRecordFrom(other);
return *this;
}
- template <typename From>
- SequenceBound<T>& operator=(SequenceBound<From>&& other) {
- // Clean up any object that we currently own.
+ // Move conversion helpers: allows upcasting from SequenceBound<Derived> to
+ // SequenceBound<Base>.
+ template <typename U>
+ // NOLINTNEXTLINE(google-explicit-constructor): Intentionally implicit.
+ SequenceBound(SequenceBound<U, CrossThreadTraits>&& other) {
+ // TODO(https://crbug.com/1382549): static_assert that U* is convertible to
+ // T*.
+ MoveRecordFrom(other);
+ }
+
+ template <typename U>
+ SequenceBound& operator=(SequenceBound<U, CrossThreadTraits>&& other) {
+ // TODO(https://crbug.com/1382549): static_assert that U* is convertible to
+ // T*.
Reset();
MoveRecordFrom(other);
return *this;
}
- // Move everything from |other|, doing pointer adjustment as needed.
- // This method is marked as NO_SANITIZE since (a) it might run before the
- // posted ctor runs on |impl_task_runner_|, and (b) implicit conversions to
- // non-virtual base classes are allowed before construction by the standard.
- // See http://eel.is/c++draft/basic.life#6 for more information.
- template <typename From>
- void MoveRecordFrom(From&& other) NO_SANITIZE("cfi-unrelated-cast") {
- // |other| might be is_null(), but that's okay.
- impl_task_runner_ = std::move(other.impl_task_runner_);
-
- // Note that static_cast<> isn't, in general, safe, since |other| might not
- // be constructed yet. Implicit conversion is supported, as long as it
- // doesn't convert to a virtual base. Of course, it allows only upcasts.
- t_ = other.t_;
-
- // The original storage is kept unmodified, so we can free it later.
- storage_ = other.storage_;
-
- other.storage_ = nullptr;
- other.t_ = nullptr;
- }
-
- // Post a call to |method| to |impl_task_runner_|.
+ // Constructs a new managed instance of `T` on `task_runner`. If `this` is
+ // already managing another instance of `T`, that pre-existing instance will
+ // first be destroyed by calling `Reset()`.
+ //
+ // Once `emplace()` returns, it is safe to immediately use `AsyncCall()`,
+ // et cetera; these calls will be sequenced after the construction of the
+ // managed `T`.
template <typename... Args>
- void Post(const base::Location& from_here,
- void (T::*method)(Args...),
- Args&&... args) const {
- impl_task_runner_->PostTask(
- from_here, base::BindOnce(
- [](void (T::*method)(Args...), T* t, Args... args) {
- (t->*method)(std::forward<Args>(args)...);
- },
- std::move(method), base::Unretained(t_),
- std::forward<Args>(args)...));
+ SequenceBound& emplace(scoped_refptr<SequencedTaskRunner> task_runner,
+ Args&&... args) {
+ Reset();
+ impl_task_runner_ = std::move(task_runner);
+ storage_.Construct(*impl_task_runner_, std::forward<Args>(args)...);
+ return *this;
+ }
+
+ // Invokes `method` of the managed `T` on `impl_task_runner_`. May only be
+ // used when `is_null()` is false.
+ //
+ // Basic usage:
+ //
+ // helper.AsyncCall(&IOHelper::DoWork);
+ //
+ // If `method` accepts arguments, use `WithArgs()` to bind them:
+ //
+ // helper.AsyncCall(&IOHelper::DoWorkWithArgs)
+ // .WithArgs(args);
+ //
+ // Use `Then()` to run a callback on the owner sequence after `method`
+ // completes:
+ //
+ // helper.AsyncCall(&IOHelper::GetValue)
+ // .Then(std::move(process_result_callback));
+ //
+ // If a method returns a non-void type, use of `Then()` is required, and the
+ // method's return value will be passed to the `Then()` callback. To ignore
+ // the method's return value instead, wrap `method` in `base::IgnoreResult()`:
+ //
+ // // Calling `GetPrefs` to force-initialize prefs.
+ // helper.AsyncCall(base::IgnoreResult(&IOHelper::GetPrefs));
+ //
+ // `WithArgs()` and `Then()` may also be combined:
+ //
+ // // Ordering is important: `Then()` must come last.
+ // helper.AsyncCall(&IOHelper::GetValueWithArgs)
+ // .WithArgs(args)
+ // .Then(std::move(process_result_callback));
+ //
+ // Note: internally, `AsyncCall()` is implemented using a series of helper
+ // classes that build the callback chain and post it on destruction. Capturing
+ // the return value and passing it elsewhere or triggering lifetime extension
+ // (e.g. by binding the return value to a reference) are both unsupported.
+ template <typename R,
+ typename C,
+ typename... Args,
+ typename = std::enable_if_t<std::is_base_of_v<C, UnwrappedT>>>
+ auto AsyncCall(R (C::*method)(Args...),
+ const Location& location = Location::Current()) const {
+ return AsyncCallBuilder<R (C::*)(Args...), R, std::tuple<Args...>>(
+ this, &location, method);
+ }
+
+ template <typename R,
+ typename C,
+ typename... Args,
+ typename = std::enable_if_t<std::is_base_of_v<C, UnwrappedT>>>
+ auto AsyncCall(R (C::*method)(Args...) const,
+ const Location& location = Location::Current()) const {
+ return AsyncCallBuilder<R (C::*)(Args...) const, R, std::tuple<Args...>>(
+ this, &location, method);
+ }
+
+ template <typename R,
+ typename C,
+ typename... Args,
+ typename = std::enable_if_t<std::is_base_of_v<C, UnwrappedT>>>
+ auto AsyncCall(internal::IgnoreResultHelper<R (C::*)(Args...) const> method,
+ const Location& location = Location::Current()) const {
+ return AsyncCallBuilder<
+ internal::IgnoreResultHelper<R (C::*)(Args...) const>, void,
+ std::tuple<Args...>>(this, &location, method);
+ }
+
+ template <typename R,
+ typename C,
+ typename... Args,
+ typename = std::enable_if_t<std::is_base_of_v<C, UnwrappedT>>>
+ auto AsyncCall(internal::IgnoreResultHelper<R (C::*)(Args...)> method,
+ const Location& location = Location::Current()) const {
+ return AsyncCallBuilder<internal::IgnoreResultHelper<R (C::*)(Args...)>,
+ void, std::tuple<Args...>>(this, &location, method);
+ }
+
+ // Posts `task` to `impl_task_runner_`, passing it a reference to the wrapped
+ // object. This allows arbitrary logic to be safely executed on the object's
+ // task runner. The object is guaranteed to remain alive for the duration of
+ // the task.
+ // TODO(crbug.com/1182140): Consider checking whether the task runner can run
+ // tasks in current sequence, and using "plain" binds and task posting (here
+ // and other places that `CrossThreadTraits::PostTask`).
+ using ConstPostTaskCallback = CrossThreadTask<void(const UnwrappedT&)>;
+ void PostTaskWithThisObject(
+ ConstPostTaskCallback callback,
+ const Location& location = Location::Current()) const {
+ DCHECK(!is_null());
+ // Even though the lifetime of the object pointed to by `get()` may not have
+ // begun yet, the storage has been allocated. Per [basic.life/6] and
+ // [basic.life/7], "Indirection through such a pointer is permitted but the
+ // resulting lvalue may only be used in limited ways, as described below."
+ CrossThreadTraits::PostTask(
+ *impl_task_runner_, location,
+ CrossThreadTraits::BindOnce(std::move(callback),
+ std::cref(*storage_.get())));
+ }
+
+ // Same as above, but for non-const operations. The callback takes a pointer
+ // to the wrapped object rather than a const ref.
+ using PostTaskCallback = CrossThreadTask<void(UnwrappedT*)>;
+ void PostTaskWithThisObject(
+ PostTaskCallback callback,
+ const Location& location = Location::Current()) const {
+ DCHECK(!is_null());
+ CrossThreadTraits::PostTask(
+ *impl_task_runner_, location,
+ CrossThreadTraits::BindOnce(
+ std::move(callback),
+ CrossThreadTraits::Unretained(storage_.get())));
+ }
+
+ void FlushPostedTasksForTesting() const {
+ DCHECK(!is_null());
+ RunLoop run_loop;
+ CrossThreadTraits::PostTask(*impl_task_runner_, FROM_HERE,
+ run_loop.QuitClosure());
+ run_loop.Run();
}
// TODO(liberato): Add PostOrCall(), to support cases where synchronous calls
// are okay if it's the same task runner.
- // TODO(liberato): Add PostAndReply()
-
- // TODO(liberato): Allow creation of callbacks that bind to a weak pointer,
- // and thread-hop to |impl_task_runner_| if needed.
-
- // Post destruction of any object we own, and return to the null state.
+ // Resets `this` to null. If `this` is not currently null, posts destruction
+ // of the managed `T` to `impl_task_runner_`.
void Reset() {
if (is_null())
return;
- // Destruct the object on the impl thread.
- impl_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&DeleteOwnerRecord, base::Unretained(t_),
- base::Unretained(storage_)));
-
+ storage_.Destruct(*impl_task_runner_);
impl_task_runner_ = nullptr;
- t_ = nullptr;
- storage_ = nullptr;
}
- // Return whether we own anything. Note that this does not guarantee that any
- // previously owned object has been destroyed. In particular, it will return
- // true immediately after a call to Reset(), though the underlying object
- // might still be pending destruction on the impl thread.
- bool is_null() const { return !t_; }
+ // Resets `this` to null. If `this` is not currently null, posts destruction
+ // of the managed `T` to `impl_task_runner_`. Blocks until the destructor has
+ // run.
+ void SynchronouslyResetForTest() {
+ if (is_null())
+ return;
+
+ scoped_refptr<SequencedTaskRunner> task_runner = impl_task_runner_;
+ Reset();
+ // `Reset()` posts a task to destroy the managed `T`; synchronously wait for
+ // that posted task to complete.
+ RunLoop run_loop;
+ CrossThreadTraits::PostTask(*task_runner, FROM_HERE,
+ run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ // Return true if `this` is logically null; otherwise, returns false.
+ //
+ // A SequenceBound is logically null if there is no managed `T`; it is only
+ // valid to call `AsyncCall()` on a non-null SequenceBound.
+ //
+ // Note that the concept of 'logically null' here does not exactly match the
+ // lifetime of `T`, which lives on `impl_task_runner_`. In particular, when
+ // SequenceBound is first constructed, `is_null()` may return false, even
+ // though the lifetime of `T` may not have begun yet on `impl_task_runner_`.
+ // Similarly, after `SequenceBound::Reset()`, `is_null()` may return true,
+ // even though the lifetime of `T` may not have ended yet on
+ // `impl_task_runner_`.
+ bool is_null() const { return !storage_.get(); }
+
+ // True if `this` is not logically null. See `is_null()`.
+ explicit operator bool() const { return !is_null(); }
private:
- // Pointer to the object, Pointer may be modified on the owning thread.
- T* t_ = nullptr;
-
- // Original allocated storage for the object.
- void* storage_ = nullptr;
-
- // The task runner on which all access to |t_| should happen.
- scoped_refptr<base::SequencedTaskRunner> impl_task_runner_;
-
// For move conversion.
- template <typename U>
+ template <typename U, typename V>
friend class SequenceBound;
- // Run on impl thread to construct |t|'s storage.
- template <typename... Args>
- static void ConstructOwnerRecord(T* t, Args&&... args) {
- new (t) T(std::forward<Args>(args)...);
+ template <template <typename> class CallbackType>
+ using EnableIfIsCrossThreadTask =
+ typename CrossThreadTraits::template EnableIfIsCrossThreadTask<
+ CallbackType>;
+
+ // Support helpers for `AsyncCall()` implementation.
+ //
+ // Several implementation notes:
+ // 1. Tasks are posted via destroying the builder or an explicit call to
+ // `Then()`.
+ //
+ // 2. A builder may be consumed by:
+ //
+ // - calling `Then()`, which immediately posts the task chain
+ // - calling `WithArgs()`, which returns a new builder with the captured
+ // arguments
+ //
+ // Builders that are consumed have the internal `sequence_bound_` field
+ // nulled out; the hope is the compiler can see this and use it to
+ // eliminate dead branches (e.g. correctness checks that aren't needed
+ // since the code can be statically proved correct).
+ //
+ // 3. Builder methods are rvalue-qualified to try to enforce that the builder
+ // is only used as a temporary. Note that this only helps so much; nothing
+ // prevents a determined caller from using `std::move()` to force calls to
+ // a non-temporary instance.
+ //
+ // TODO(dcheng): It might also be possible to use Gmock-style matcher
+ // composition, e.g. something like:
+ //
+ // sb.AsyncCall(&Helper::DoWork, WithArgs(args),
+ // Then(std::move(process_result));
+ //
+ // In theory, this might allow the elimination of magic destructors and
+ // better static checking by the compiler.
+ template <typename MethodRef>
+ class AsyncCallBuilderBase {
+ protected:
+ AsyncCallBuilderBase(const SequenceBound* sequence_bound,
+ const Location* location,
+ MethodRef method)
+ : sequence_bound_(sequence_bound),
+ location_(location),
+ method_(method) {
+ // Common entry point for `AsyncCall()`, so check preconditions here.
+ DCHECK(sequence_bound_);
+ DCHECK(sequence_bound_->storage_.get());
+ }
+
+ AsyncCallBuilderBase(AsyncCallBuilderBase&&) = default;
+ AsyncCallBuilderBase& operator=(AsyncCallBuilderBase&&) = default;
+
+ // `sequence_bound_` is consumed and set to `nullptr` when `Then()` is
+ // invoked. This is used as a flag for two potential states
+ //
+ // - if a method returns void, invoking `Then()` is optional. The destructor
+ // will check if `sequence_bound_` is null; if it is, `Then()` was
+ // already invoked and the task chain has already been posted, so the
+ // destructor does not need to do anything. Otherwise, the destructor
+ // needs to post the task to make the async call. In theory, the compiler
+ // should be able to eliminate this branch based on the presence or
+ // absence of a call to `Then()`.
+ //
+ // - if a method returns a non-void type, `Then()` *must* be invoked. The
+ // destructor will `CHECK()` if `sequence_bound_` is non-null, since that
+ // indicates `Then()` was not invoked. Similarly, note this branch should
+ // be eliminated by the optimizer if the code is free of bugs. :)
+ raw_ptr<const SequenceBound<T, CrossThreadTraits>, DanglingUntriaged>
+ sequence_bound_;
+ // Subtle: this typically points at a Location *temporary*. This is used to
+ // try to detect errors resulting from lifetime extension of the async call
+ // factory temporaries, since the factory destructors can perform work. If
+ // the lifetime of the factory is incorrectly extended, dereferencing
+ // `location_` will trigger a stack-use-after-scope when running with ASan.
+ const raw_ptr<const Location> location_;
+ MethodRef method_;
+ };
+
+ template <typename MethodRef, typename ReturnType, typename ArgsTuple>
+ class AsyncCallBuilderImpl;
+
+ // Selected method has no arguments and returns void.
+ template <typename MethodRef>
+ class AsyncCallBuilderImpl<MethodRef, void, std::tuple<>>
+ : public AsyncCallBuilderBase<MethodRef> {
+ public:
+ // Note: despite being here, this is actually still protected, since it is
+ // protected on the base class.
+ using AsyncCallBuilderBase<MethodRef>::AsyncCallBuilderBase;
+
+ ~AsyncCallBuilderImpl() {
+ if (this->sequence_bound_) {
+ CrossThreadTraits::PostTask(
+ *this->sequence_bound_->impl_task_runner_, *this->location_,
+ CrossThreadTraits::BindOnce(
+ this->method_, CrossThreadTraits::Unretained(
+ this->sequence_bound_->storage_.get())));
+ }
+ }
+
+ void Then(CrossThreadTask<void()> then_callback) && {
+ this->sequence_bound_->PostTaskAndThenHelper(
+ *this->location_,
+ CrossThreadTraits::BindOnce(
+ this->method_, CrossThreadTraits::Unretained(
+ this->sequence_bound_->storage_.get())),
+ std::move(then_callback));
+ this->sequence_bound_ = nullptr;
+ }
+
+ private:
+ friend SequenceBound;
+
+ AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
+ AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
+ };
+
+ // Selected method has no arguments and returns non-void.
+ template <typename MethodRef, typename ReturnType>
+ class AsyncCallBuilderImpl<MethodRef, ReturnType, std::tuple<>>
+ : public AsyncCallBuilderBase<MethodRef> {
+ public:
+ // Note: despite being here, this is actually still protected, since it is
+ // protected on the base class.
+ using AsyncCallBuilderBase<MethodRef>::AsyncCallBuilderBase;
+
+ ~AsyncCallBuilderImpl() {
+ // Must use Then() since the method's return type is not void.
+ // Should be optimized out if the code is bug-free.
+ CHECK(!this->sequence_bound_)
+ << "Then() not invoked for a method that returns a non-void type; "
+ << "make sure to invoke Then() or use base::IgnoreResult()";
+ }
+
+ template <template <typename> class CallbackType,
+ typename ThenArg,
+ typename = EnableIfIsCrossThreadTask<CallbackType>>
+ void Then(CallbackType<void(ThenArg)> then_callback) && {
+ this->sequence_bound_->PostTaskAndThenHelper(
+ *this->location_,
+ CrossThreadTraits::BindOnce(
+ this->method_, CrossThreadTraits::Unretained(
+ this->sequence_bound_->storage_.get())),
+ std::move(then_callback));
+ this->sequence_bound_ = nullptr;
+ }
+
+ private:
+ friend SequenceBound;
+
+ AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
+ AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
+ };
+
+ // Selected method has arguments. Return type can be void or non-void.
+ template <typename MethodRef, typename ReturnType, typename... Args>
+ class AsyncCallBuilderImpl<MethodRef, ReturnType, std::tuple<Args...>>
+ : public AsyncCallBuilderBase<MethodRef> {
+ public:
+ // Note: despite being here, this is actually still protected, since it is
+ // protected on the base class.
+ using AsyncCallBuilderBase<MethodRef>::AsyncCallBuilderBase;
+
+ ~AsyncCallBuilderImpl() {
+ // Must use WithArgs() since the method takes arguments.
+ // Should be optimized out if the code is bug-free.
+ CHECK(!this->sequence_bound_);
+ }
+
+ template <typename... BoundArgs>
+ auto WithArgs(BoundArgs&&... bound_args) {
+ const SequenceBound* const sequence_bound =
+ std::exchange(this->sequence_bound_, nullptr);
+ return AsyncCallWithBoundArgsBuilder<ReturnType>(
+ sequence_bound, this->location_,
+ CrossThreadTraits::BindOnce(
+ this->method_,
+ CrossThreadTraits::Unretained(sequence_bound->storage_.get()),
+ std::forward<BoundArgs>(bound_args)...));
+ }
+
+ private:
+ friend SequenceBound;
+
+ AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
+ AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
+ };
+
+ // `MethodRef` is either a member function pointer type or a member function
+ // pointer type wrapped with `internal::IgnoreResultHelper`.
+ // `R` is the return type of `MethodRef`. This is always `void` if
+ // `MethodRef` is an `internal::IgnoreResultHelper` wrapper.
+ // `ArgsTuple` is a `std::tuple` with template type arguments corresponding to
+ // the types of the method's parameters.
+ template <typename MethodRef, typename R, typename ArgsTuple>
+ using AsyncCallBuilder = AsyncCallBuilderImpl<MethodRef, R, ArgsTuple>;
+
+ // Support factories when arguments are bound using `WithArgs()`. These
+ // factories don't need to handle raw method pointers, since everything has
+ // already been packaged into a base::OnceCallback.
+ template <typename ReturnType>
+ class AsyncCallWithBoundArgsBuilderBase {
+ protected:
+ AsyncCallWithBoundArgsBuilderBase(const SequenceBound* sequence_bound,
+ const Location* location,
+ CrossThreadTask<ReturnType()> callback)
+ : sequence_bound_(sequence_bound),
+ location_(location),
+ callback_(std::move(callback)) {
+ DCHECK(sequence_bound_);
+ DCHECK(sequence_bound_->storage_.get());
+ }
+
+ // Subtle: the internal helpers rely on move elision. Preventing move
+ // elision (e.g. using `std::move()` when returning the temporary) will
+ // trigger a `CHECK()` since `sequence_bound_` is not reset to nullptr on
+ // move.
+ AsyncCallWithBoundArgsBuilderBase(
+ AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
+ AsyncCallWithBoundArgsBuilderBase& operator=(
+ AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
+
+ raw_ptr<const SequenceBound<T, CrossThreadTraits>> sequence_bound_;
+ const raw_ptr<const Location> location_;
+ CrossThreadTask<ReturnType()> callback_;
+ };
+
+ // Note: this doesn't handle a void return type, which has an explicit
+ // specialization below.
+ template <typename ReturnType>
+ class AsyncCallWithBoundArgsBuilderDefault
+ : public AsyncCallWithBoundArgsBuilderBase<ReturnType> {
+ public:
+ ~AsyncCallWithBoundArgsBuilderDefault() {
+ // Must use Then() since the method's return type is not void.
+ // Should be optimized out if the code is bug-free.
+ CHECK(!this->sequence_bound_);
+ }
+
+ template <template <typename> class CallbackType,
+ typename ThenArg,
+ typename = EnableIfIsCrossThreadTask<CallbackType>>
+ void Then(CallbackType<void(ThenArg)> then_callback) && {
+ this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
+ std::move(this->callback_),
+ std::move(then_callback));
+ this->sequence_bound_ = nullptr;
+ }
+
+ protected:
+ using AsyncCallWithBoundArgsBuilderBase<
+ ReturnType>::AsyncCallWithBoundArgsBuilderBase;
+
+ private:
+ friend SequenceBound;
+
+ AsyncCallWithBoundArgsBuilderDefault(
+ AsyncCallWithBoundArgsBuilderDefault&&) = default;
+ AsyncCallWithBoundArgsBuilderDefault& operator=(
+ AsyncCallWithBoundArgsBuilderDefault&&) = default;
+ };
+
+ class AsyncCallWithBoundArgsBuilderVoid
+ : public AsyncCallWithBoundArgsBuilderBase<void> {
+ public:
+ // Note: despite being here, this is actually still protected, since it is
+ // protected on the base class.
+ using AsyncCallWithBoundArgsBuilderBase<
+ void>::AsyncCallWithBoundArgsBuilderBase;
+
+ ~AsyncCallWithBoundArgsBuilderVoid() {
+ if (this->sequence_bound_) {
+ CrossThreadTraits::PostTask(*this->sequence_bound_->impl_task_runner_,
+ *this->location_,
+ std::move(this->callback_));
+ }
+ }
+
+ void Then(CrossThreadTask<void()> then_callback) && {
+ this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
+ std::move(this->callback_),
+ std::move(then_callback));
+ this->sequence_bound_ = nullptr;
+ }
+
+ private:
+ friend SequenceBound;
+
+ AsyncCallWithBoundArgsBuilderVoid(AsyncCallWithBoundArgsBuilderVoid&&) =
+ default;
+ AsyncCallWithBoundArgsBuilderVoid& operator=(
+ AsyncCallWithBoundArgsBuilderVoid&&) = default;
+ };
+
+ template <typename ReturnType>
+ using AsyncCallWithBoundArgsBuilder = typename std::conditional<
+ std::is_void<ReturnType>::value,
+ AsyncCallWithBoundArgsBuilderVoid,
+ AsyncCallWithBoundArgsBuilderDefault<ReturnType>>::type;
+
+ void PostTaskAndThenHelper(const Location& location,
+ CrossThreadTask<void()> callback,
+ CrossThreadTask<void()> then_callback) const {
+ CrossThreadTraits::PostTaskAndReply(*impl_task_runner_, location,
+ std::move(callback),
+ std::move(then_callback));
}
- // Destruct the object associated with |t|, and delete |storage|.
- static void DeleteOwnerRecord(T* t, void* storage) {
- t->~T();
- AlignedFree(storage);
+ template <typename ReturnType,
+ template <typename>
+ class CallbackType,
+ typename ThenArg,
+ typename = EnableIfIsCrossThreadTask<CallbackType>>
+ void PostTaskAndThenHelper(const Location& location,
+ CrossThreadTask<ReturnType()> callback,
+ CallbackType<void(ThenArg)> then_callback) const {
+ CrossThreadTask<void(ThenArg)>&& once_then_callback =
+ std::move(then_callback);
+ CrossThreadTraits::PostTaskAndReplyWithResult(
+ *impl_task_runner_, location, std::move(callback),
+ std::move(once_then_callback));
}
- // To preserve ownership semantics, we disallow copy construction / copy
- // assignment. Move construction / assignment is fine.
- DISALLOW_COPY_AND_ASSIGN(SequenceBound);
+ // Helper to support move construction and move assignment.
+ //
+ // TODO(https://crbug.com/1382549): Constrain this so converting between
+ // std::unique_ptr<T> and T are explicitly forbidden (rather than simply
+ // failing to build in spectacular ways).
+ template <typename From>
+ void MoveRecordFrom(From&& other) {
+ impl_task_runner_ = std::move(other.impl_task_runner_);
+
+ storage_.TakeFrom(std::move(other.storage_));
+ }
+
+ Storage storage_;
+
+ // Task runner which manages `storage_.get()`. `storage_.get()`'s pointee is
+ // constructed, destroyed, and otherwise used only on this task runner.
+ scoped_refptr<SequencedTaskRunner> impl_task_runner_;
};
} // namespace base
diff --git a/base/threading/sequence_bound_internal.h b/base/threading/sequence_bound_internal.h
new file mode 100644
index 0000000..0e4b37c
--- /dev/null
+++ b/base/threading/sequence_bound_internal.h
@@ -0,0 +1,201 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
+#define BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
+#include "base/location.h"
+#include "base/memory/aligned_memory.h"
+#include "base/memory/raw_ptr.h"
+#include "base/task/sequenced_task_runner.h"
+
+namespace base::sequence_bound_internal {
+
+struct CrossThreadTraits {
+ template <typename Signature>
+ using CrossThreadTask = OnceCallback<Signature>;
+
+ template <typename Functor, typename... Args>
+ static inline auto BindOnce(Functor&& functor, Args&&... args) {
+ return ::base::BindOnce(std::forward<Functor>(functor),
+ std::forward<Args>(args)...);
+ }
+
+ template <typename T>
+ static inline auto Unretained(T ptr) {
+ return ::base::Unretained(ptr);
+ }
+
+ static inline bool PostTask(SequencedTaskRunner& task_runner,
+ const Location& location,
+ OnceClosure&& task) {
+ return task_runner.PostTask(location, std::move(task));
+ }
+
+ static inline bool PostTaskAndReply(SequencedTaskRunner& task_runner,
+ const Location& location,
+ OnceClosure&& task,
+ OnceClosure&& reply) {
+ return task_runner.PostTaskAndReply(location, std::move(task),
+ std::move(reply));
+ }
+
+ template <typename TaskReturnType, typename ReplyArgType>
+ static inline bool PostTaskAndReplyWithResult(
+ SequencedTaskRunner& task_runner,
+ const Location& location,
+ OnceCallback<TaskReturnType()>&& task,
+ OnceCallback<void(ReplyArgType)>&& reply) {
+ return task_runner.PostTaskAndReplyWithResult(location, std::move(task),
+ std::move(reply));
+ }
+
+ // Accept RepeatingCallback here since it's convertible to a OnceCallback.
+ template <template <typename> class CallbackType>
+ using EnableIfIsCrossThreadTask = EnableIfIsBaseCallback<CallbackType>;
+};
+
+template <typename T, typename CrossThreadTraits>
+class Storage {
+ public:
+ using Ptr = T*;
+
+ Ptr get() const { return ptr_; }
+
+ // Marked NO_SANITIZE because cfi doesn't like casting uninitialized memory to
+ // `T*`. However, this is safe here because:
+ //
+ // 1. The cast is well-defined (see https://eel.is/c++draft/basic.life#6) and
+ // 2. The resulting pointer is only ever dereferenced on `task_runner`.
+ // By the time SequenceBound's constructor returns, the task to construct
+ // `T` will already be posted; thus, subsequent dereference of `ptr_` on
+ // `task_runner` are safe.
+ template <typename... Args>
+ NO_SANITIZE("cfi-unrelated-cast")
+ void Construct(SequencedTaskRunner& task_runner, Args&&... args) {
+ // TODO(https://crbug.com/1382549): Use universal forwarding and assert that
+ // T is constructible from args for better error messages.
+ DCHECK(!alloc_);
+ DCHECK(!ptr_);
+
+ // Allocate space for but do not construct an instance of `T`.
+ // AlignedAlloc() requires alignment be a multiple of sizeof(void*).
+ alloc_ = AlignedAlloc(
+ sizeof(T), sizeof(void*) > alignof(T) ? sizeof(void*) : alignof(T));
+ ptr_ = reinterpret_cast<Ptr>(alloc_.get());
+
+ // Ensure that `ptr_` will be initialized.
+ CrossThreadTraits::PostTask(
+ task_runner, FROM_HERE,
+ CrossThreadTraits::BindOnce(&InternalConstruct<Args...>,
+ CrossThreadTraits::Unretained(ptr_),
+ std::forward<Args>(args)...));
+ }
+
+ // Marked NO_SANITIZE since:
+ // 1. SequenceBound can be moved before `ptr_` is constructed on its managing
+ // `SequencedTaskRunner` but
+ // 2. Implicit conversions to non-virtual base classes are allowed before the
+ // lifetime of the object that `ptr_` points at has begun (see
+ // https://eel.is/c++draft/basic.life#6).
+ template <typename U>
+ NO_SANITIZE("cfi-unrelated-cast")
+ void TakeFrom(Storage<U, CrossThreadTraits>&& other) {
+ // Subtle: this must not use static_cast<>, since the lifetime of the
+ // managed `T` may not have begun yet. However, the standard explicitly
+ // still allows implicit conversion to a non-virtual base class.
+ ptr_ = std::exchange(other.ptr_, nullptr);
+ alloc_ = std::exchange(other.alloc_, nullptr);
+ }
+
+ void Destruct(SequencedTaskRunner& task_runner) {
+ CrossThreadTraits::PostTask(
+ task_runner, FROM_HERE,
+ CrossThreadTraits::BindOnce(
+ &InternalDestruct, CrossThreadTraits::Unretained(ptr_),
+ CrossThreadTraits::Unretained(alloc_.get())));
+ ptr_ = nullptr;
+ alloc_ = nullptr;
+ }
+
+ private:
+ // Needed to allow conversions from compatible `U`s.
+ template <typename U, typename V>
+ friend class Storage;
+
+ // Helpers for constructing and destroying `T` on its managing
+ // `SequencedTaskRunner`.
+ template <typename... Args>
+ static void InternalConstruct(T* ptr, std::decay_t<Args>&&... args) {
+ new (ptr) T(std::move(args)...);
+ }
+
+ static void InternalDestruct(T* ptr, void* alloc) {
+ ptr->~T();
+ AlignedFree(alloc);
+ }
+
+ // Pointer to the managed `T`.
+ Ptr ptr_ = nullptr;
+
+ // Storage originally allocated by `AlignedAlloc()`. Maintained separately
+ // from `ptr_` since the original, unadjusted pointer needs to be passed to
+ // `AlignedFree()`.
+ raw_ptr<void, DanglingUntriaged> alloc_ = nullptr;
+};
+
+template <typename T, typename CrossThreadTraits>
+struct Storage<std::unique_ptr<T>, CrossThreadTraits> {
+ public:
+ using Ptr = T*;
+
+ Ptr get() const { return ptr_; }
+
+ template <typename U>
+ void Construct(SequencedTaskRunner& task_runner, std::unique_ptr<U> arg) {
+ // TODO(https://crbug.com/1382549): Use universal forwarding and assert that
+ // there is one arg that is a unique_ptr for better error messages.
+ DCHECK(!ptr_);
+
+ ptr_ = arg.release();
+ // No additional storage needs to be allocated since `T` is already
+ // constructed and lives on the heap.
+ }
+
+ template <typename U>
+ void TakeFrom(Storage<std::unique_ptr<U>, CrossThreadTraits>&& other) {
+ ptr_ = std::exchange(other.ptr_, nullptr);
+ }
+
+ void Destruct(SequencedTaskRunner& task_runner) {
+ CrossThreadTraits::PostTask(
+ task_runner, FROM_HERE,
+ CrossThreadTraits::BindOnce(&InternalDestruct,
+ CrossThreadTraits::Unretained(ptr_)));
+
+ ptr_ = nullptr;
+ }
+
+ private:
+ // Needed to allow conversions from compatible `U`s.
+ template <typename U, typename V>
+ friend class Storage;
+
+ static void InternalDestruct(T* ptr) { delete ptr; }
+
+ // Pointer to the heap-allocated `T`.
+ Ptr ptr_ = nullptr;
+};
+
+} // namespace base::sequence_bound_internal
+
+#endif // BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
diff --git a/base/threading/sequence_bound_unittest.cc b/base/threading/sequence_bound_unittest.cc
index 9288b06..5d9a718 100644
--- a/base/threading/sequence_bound_unittest.cc
+++ b/base/threading/sequence_bound_unittest.cc
@@ -1,317 +1,1152 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/sequence_bound.h"
+#include <functional>
+#include <memory>
+#include <utility>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/raw_ref.h"
#include "base/run_loop.h"
-#include "base/test/scoped_task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/sequence_checker.h"
+#include "base/strings/stringprintf.h"
+#include "base/synchronization/lock.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/thread_pool.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
-class SequenceBoundTest : public ::testing::Test {
+namespace {
+
+class EventLogger {
public:
- // Helpful values that our test classes use.
- enum Value {
- kInitialValue = 0,
- kDifferentValue = 1,
+ EventLogger() = default;
- // Values used by the Derived class.
- kDerivedCtorValue = 111,
- kDerivedDtorValue = 222,
+ void AddEvent(StringPiece event) {
+ AutoLock guard(lock_);
+ events_.push_back(std::string(event));
+ }
+ std::vector<std::string> TakeEvents() {
+ AutoLock guard(lock_);
+ return std::exchange(events_, {});
+ }
- // Values used by the Other class.
- kOtherCtorValue = 333,
- kOtherDtorValue = 444,
- };
-
- void SetUp() override { task_runner_ = base::ThreadTaskRunnerHandle::Get(); }
-
- void TearDown() override { scoped_task_environment_.RunUntilIdle(); }
-
- // Do-nothing base class, just so we can test assignment of derived classes.
- // It introduces a virtual destructor, so that casting derived classes to
- // Base should still use the appropriate (virtual) destructor.
- class Base {
- public:
- virtual ~Base() {}
- };
-
- // Handy class to set an int ptr to different values, to verify that things
- // are being run properly.
- class Derived : public Base {
- public:
- Derived(Value* ptr) : ptr_(ptr) { *ptr_ = kDerivedCtorValue; }
- ~Derived() override { *ptr_ = kDerivedDtorValue; }
- void SetValue(Value value) { *ptr_ = value; }
- Value* ptr_;
- };
-
- // Another base class, which sets ints to different values.
- class Other {
- public:
- Other(Value* ptr) : ptr_(ptr) { *ptr = kOtherCtorValue; };
- virtual ~Other() { *ptr_ = kOtherDtorValue; }
- void SetValue(Value value) { *ptr_ = value; }
- Value* ptr_;
- };
-
- class MultiplyDerived : public Other, public Derived {
- public:
- MultiplyDerived(Value* ptr1, Value* ptr2) : Other(ptr1), Derived(ptr2) {}
- };
-
- struct VirtuallyDerived : public virtual Base {};
-
- base::test::ScopedTaskEnvironment scoped_task_environment_;
- scoped_refptr<base::SequencedTaskRunner> task_runner_;
- Value value = kInitialValue;
+ private:
+ Lock lock_;
+ std::vector<std::string> events_ GUARDED_BY(lock_);
};
-TEST_F(SequenceBoundTest, ConstructThenPostThenReset) {
- auto derived = SequenceBound<Derived>(task_runner_, &value);
- EXPECT_FALSE(derived.is_null());
+// Helpers for writing type tests against both `SequenceBound<T>` and
+// `SequenceBound<std::unique_ptr<T>`. The tricky part here is that the
+// constructor and emplace both need to accept variadic args; however,
+// construction of the actual `T` depends on the storage strategy. The
+// `Wrapper` template provides this layer of indirection to construct the
+// managed `T` while still passing through all the other remaining
+// `SequenceBound` APIs.
+struct DirectVariation {
+ static constexpr bool kManagingTaskRunnerConstructsT = true;
- // Nothing should happen until we run the message loop.
- EXPECT_EQ(value, kInitialValue);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedCtorValue);
+ template <typename T>
+ class Wrapper : public SequenceBound<T> {
+ public:
+ template <typename... Args>
+ explicit Wrapper(scoped_refptr<SequencedTaskRunner> task_runner,
+ Args&&... args)
+ : SequenceBound<T>(std::move(task_runner),
+ std::forward<Args>(args)...) {}
- // Post now that the object has been constructed.
- derived.Post(FROM_HERE, &Derived::SetValue, kDifferentValue);
- EXPECT_EQ(value, kDerivedCtorValue);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDifferentValue);
+ template <typename... Args>
+ void WrappedEmplace(scoped_refptr<SequencedTaskRunner> task_runner,
+ Args&&... args) {
+ this->emplace(std::move(task_runner), std::forward<Args>(args)...);
+ }
- // Reset it, and make sure that destruction is posted. The owner should
- // report that it is null immediately.
- derived.Reset();
- EXPECT_TRUE(derived.is_null());
- EXPECT_EQ(value, kDifferentValue);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedDtorValue);
+ using SequenceBound<T>::SequenceBound;
+ using SequenceBound<T>::operator=;
+
+ private:
+ using SequenceBound<T>::emplace;
+ };
+};
+
+struct UniquePtrVariation {
+ static constexpr bool kManagingTaskRunnerConstructsT = false;
+
+ template <typename T>
+ struct Wrapper : public SequenceBound<std::unique_ptr<T>> {
+ public:
+ template <typename... Args>
+ explicit Wrapper(scoped_refptr<SequencedTaskRunner> task_runner,
+ Args&&... args)
+ : SequenceBound<std::unique_ptr<T>>(
+ std::move(task_runner),
+ std::make_unique<T>(std::forward<Args>(args)...)) {}
+
+ template <typename... Args>
+ void WrappedEmplace(scoped_refptr<SequencedTaskRunner> task_runner,
+ Args&&... args) {
+ this->emplace(std::move(task_runner),
+ std::make_unique<T>(std::forward<Args>(args)...));
+ }
+
+ using SequenceBound<std::unique_ptr<T>>::SequenceBound;
+ using SequenceBound<std::unique_ptr<T>>::operator=;
+
+ private:
+ using SequenceBound<std::unique_ptr<T>>::emplace;
+ };
+};
+
+// Helper macros since using the name directly is otherwise quite unwieldy.
+#define SEQUENCE_BOUND_T typename TypeParam::template Wrapper
+// Try to catch tests that inadvertently use SequenceBound<T> directly instead
+// of SEQUENCE_BOUND_T, as that bypasses the point of having a typed test.
+#define SequenceBound PleaseUseSequenceBoundT
+
+template <typename Variation>
+class SequenceBoundTest : public ::testing::Test {
+ public:
+ void TearDown() override {
+ // Make sure that any objects owned by `SequenceBound` have been destroyed
+ // to avoid tripping leak detection.
+ task_environment_.RunUntilIdle();
+ }
+
+ // Helper for tests that want to synchronize on a `SequenceBound` which has
+ // already been `Reset()`: a null `SequenceBound` has no `SequencedTaskRunner`
+ // associated with it, so the usual `FlushPostedTasksForTesting()` helper does
+ // not work.
+ void FlushPostedTasks() {
+ RunLoop run_loop;
+ background_task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ test::TaskEnvironment task_environment_;
+
+ // Task runner to use for SequenceBound's managed `T`.
+ scoped_refptr<SequencedTaskRunner> background_task_runner_ =
+ ThreadPool::CreateSequencedTaskRunner({});
+
+ // Defined as part of the test fixture so that tests using `EventLogger` do
+ // not need to explicitly synchronize on `Reset() to avoid use-after-frees;
+ // instead, tests should rely on `TearDown()` to drain and run any
+ // already-posted cleanup tasks.
+ EventLogger logger_;
+};
+
+using Variations = ::testing::Types<DirectVariation, UniquePtrVariation>;
+TYPED_TEST_SUITE(SequenceBoundTest, Variations);
+
+class Base {
+ public:
+ explicit Base(EventLogger& logger) : logger_(logger) {
+ logger_->AddEvent("constructed Base");
+ }
+ virtual ~Base() { logger_->AddEvent("destroyed Base"); }
+
+ protected:
+ EventLogger& GetLogger() { return *logger_; }
+
+ private:
+ const raw_ref<EventLogger> logger_;
+};
+
+class Derived : public Base {
+ public:
+ explicit Derived(EventLogger& logger) : Base(logger) {
+ GetLogger().AddEvent("constructed Derived");
+ }
+
+ ~Derived() override { GetLogger().AddEvent("destroyed Derived"); }
+
+ void SetValue(int value) {
+ GetLogger().AddEvent(StringPrintf("set Derived to %d", value));
+ }
+};
+
+class Leftmost {
+ public:
+ explicit Leftmost(EventLogger& logger) : logger_(logger) {
+ logger_->AddEvent("constructed Leftmost");
+ }
+ virtual ~Leftmost() { logger_->AddEvent("destroyed Leftmost"); }
+
+ void SetValue(int value) {
+ logger_->AddEvent(StringPrintf("set Leftmost to %d", value));
+ }
+
+ private:
+ const raw_ref<EventLogger> logger_;
+};
+
+class Rightmost : public Base {
+ public:
+ explicit Rightmost(EventLogger& logger) : Base(logger) {
+ GetLogger().AddEvent("constructed Rightmost");
+ }
+
+ ~Rightmost() override { GetLogger().AddEvent("destroyed Rightmost"); }
+
+ void SetValue(int value) {
+ GetLogger().AddEvent(StringPrintf("set Rightmost to %d", value));
+ }
+};
+
+class MultiplyDerived : public Leftmost, public Rightmost {
+ public:
+ explicit MultiplyDerived(EventLogger& logger)
+ : Leftmost(logger), Rightmost(logger) {
+ GetLogger().AddEvent("constructed MultiplyDerived");
+ }
+
+ ~MultiplyDerived() override {
+ GetLogger().AddEvent("destroyed MultiplyDerived");
+ }
+};
+
+class BoxedValue {
+ public:
+ explicit BoxedValue(int initial_value, EventLogger* logger = nullptr)
+ : logger_(logger), value_(initial_value) {
+ sequence_checker_.DetachFromSequence();
+ AddEventIfNeeded(StringPrintf("constructed BoxedValue = %d", value_));
+ }
+
+ BoxedValue(const BoxedValue&) = delete;
+ BoxedValue& operator=(const BoxedValue&) = delete;
+
+ ~BoxedValue() {
+ EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
+ AddEventIfNeeded(StringPrintf("destroyed BoxedValue = %d", value_));
+ if (destruction_callback_)
+ std::move(destruction_callback_).Run();
+ }
+
+ void set_destruction_callback(OnceClosure callback) {
+ EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
+ destruction_callback_ = std::move(callback);
+ }
+
+ int value() const {
+ EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
+ AddEventIfNeeded(StringPrintf("accessed BoxedValue = %d", value_));
+ return value_;
+ }
+ void set_value(int value) {
+ EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
+ AddEventIfNeeded(
+ StringPrintf("updated BoxedValue from %d to %d", value_, value));
+ value_ = value;
+ }
+
+ private:
+ void AddEventIfNeeded(StringPiece event) const {
+ if (logger_) {
+ logger_->AddEvent(event);
+ }
+ }
+
+ SequenceChecker sequence_checker_;
+
+ mutable raw_ptr<EventLogger> logger_ = nullptr;
+
+ int value_ = 0;
+ OnceClosure destruction_callback_;
+};
+
+// Smoke test that all interactions with the wrapped object are posted to the
+// correct task runner.
+class SequenceValidator {
+ public:
+ explicit SequenceValidator(scoped_refptr<SequencedTaskRunner> task_runner,
+ bool constructs_on_managing_task_runner)
+ : task_runner_(std::move(task_runner)) {
+ if (constructs_on_managing_task_runner) {
+ EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
+ }
+ }
+
+ ~SequenceValidator() {
+ EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
+ }
+
+ void ReturnsVoid() const {
+ EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
+ }
+
+ void ReturnsVoidMutable() {
+ EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
+ }
+
+ int ReturnsInt() const {
+ EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
+ return 0;
+ }
+
+ int ReturnsIntMutable() {
+ EXPECT_TRUE(task_runner_->RunsTasksInCurrentSequence());
+ return 0;
+ }
+
+ private:
+ scoped_refptr<SequencedTaskRunner> task_runner_;
+};
+
+TYPED_TEST(SequenceBoundTest, SequenceValidation) {
+ SEQUENCE_BOUND_T<SequenceValidator> validator(
+ this->background_task_runner_, this->background_task_runner_,
+ TypeParam::kManagingTaskRunnerConstructsT);
+ validator.AsyncCall(&SequenceValidator::ReturnsVoid);
+ validator.AsyncCall(&SequenceValidator::ReturnsVoidMutable);
+ validator.AsyncCall(&SequenceValidator::ReturnsInt).Then(BindOnce([](int) {
+ }));
+ validator.AsyncCall(&SequenceValidator::ReturnsIntMutable)
+ .Then(BindOnce([](int) {}));
+ validator.AsyncCall(IgnoreResult(&SequenceValidator::ReturnsInt));
+ validator.AsyncCall(IgnoreResult(&SequenceValidator::ReturnsIntMutable));
+ validator.WrappedEmplace(this->background_task_runner_,
+ this->background_task_runner_,
+ TypeParam::kManagingTaskRunnerConstructsT);
+ validator.PostTaskWithThisObject(BindLambdaForTesting(
+ [](const SequenceValidator& v) { v.ReturnsVoid(); }));
+ validator.PostTaskWithThisObject(BindLambdaForTesting(
+ [](SequenceValidator* v) { v->ReturnsVoidMutable(); }));
+ validator.Reset();
+ this->FlushPostedTasks();
}
-TEST_F(SequenceBoundTest, PostBeforeConstruction) {
- // Construct an object and post a message to it, before construction has been
- // run on |task_runner_|.
- auto derived = SequenceBound<Derived>(task_runner_, &value);
- derived.Post(FROM_HERE, &Derived::SetValue, kDifferentValue);
- EXPECT_EQ(value, kInitialValue);
- // Both construction and SetValue should run.
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDifferentValue);
+TYPED_TEST(SequenceBoundTest, Basic) {
+ SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0,
+ &this->logger_);
+ // Construction of `BoxedValue` may be posted to `background_task_runner_`,
+ // but the `SequenceBound` itself should immediately be treated as valid /
+ // non-null.
+ EXPECT_FALSE(value.is_null());
+ EXPECT_TRUE(value);
+ value.FlushPostedTasksForTesting();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed BoxedValue = 0"));
+
+ value.AsyncCall(&BoxedValue::set_value).WithArgs(66);
+ value.FlushPostedTasksForTesting();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("updated BoxedValue from 0 to 66"));
+
+ // Destruction of `BoxedValue` may be posted to `background_task_runner_`, but
+ // the `SequenceBound` itself should immediately be treated as valid /
+ // non-null.
+ value.Reset();
+ EXPECT_TRUE(value.is_null());
+ EXPECT_FALSE(value);
+ this->FlushPostedTasks();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("destroyed BoxedValue = 66"));
}
-TEST_F(SequenceBoundTest, MoveConstructionFromSameClass) {
- // Verify that we can move-construct with the same class.
- auto derived_old = SequenceBound<Derived>(task_runner_, &value);
- auto derived_new = std::move(derived_old);
+TYPED_TEST(SequenceBoundTest, ConstructAndImmediateAsyncCall) {
+ // Calling `AsyncCall` immediately after construction should always work.
+ SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0,
+ &this->logger_);
+ value.AsyncCall(&BoxedValue::set_value).WithArgs(8);
+ value.FlushPostedTasksForTesting();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed BoxedValue = 0",
+ "updated BoxedValue from 0 to 8"));
+}
+
+TYPED_TEST(SequenceBoundTest, MoveConstruction) {
+ // std::ref() is required here: internally, the async work is bound into the
+ // standard base callback infrastructure, which requires the explicit use of
+ // `std::cref()` and `std::ref()` when passing by reference.
+ SEQUENCE_BOUND_T<Derived> derived_old(this->background_task_runner_,
+ std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Derived> derived_new = std::move(derived_old);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(derived_old.is_null());
EXPECT_FALSE(derived_new.is_null());
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedCtorValue);
-
- // Verify that |derived_new| owns the object now, and that the virtual
- // destructor is called.
derived_new.Reset();
- EXPECT_EQ(value, kDerivedCtorValue);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedDtorValue);
+ this->FlushPostedTasks();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed Base", "constructed Derived",
+ "destroyed Derived", "destroyed Base"));
}
-TEST_F(SequenceBoundTest, MoveConstructionFromDerivedClass) {
- // Verify that we can move-construct to a base class from a derived class.
- auto derived = SequenceBound<Derived>(task_runner_, &value);
- SequenceBound<Base> base(std::move(derived));
+TYPED_TEST(SequenceBoundTest, MoveConstructionUpcastsToBase) {
+ SEQUENCE_BOUND_T<Derived> derived(this->background_task_runner_,
+ std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Base> base = std::move(derived);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(derived.is_null());
EXPECT_FALSE(base.is_null());
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedCtorValue);
- // Verify that |base| owns the object now, and that destruction still destroys
- // Derived properly.
+ // The original `Derived` object is now owned by `SequencedBound<Base>`; make
+ // sure `~Derived()` still runs when it is reset.
base.Reset();
- EXPECT_EQ(value, kDerivedCtorValue);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedDtorValue);
+ this->FlushPostedTasks();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed Base", "constructed Derived",
+ "destroyed Derived", "destroyed Base"));
}
-TEST_F(SequenceBoundTest, MultiplyDerivedDestructionWorksLeftSuper) {
- // Verify that everything works when we're casting around in ways that might
- // change the address. We cast to the left side of MultiplyDerived and then
- // reset the owner. ASAN will catch free() errors.
- Value value2 = kInitialValue;
- auto mderived = SequenceBound<MultiplyDerived>(task_runner_, &value, &value2);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kOtherCtorValue);
- EXPECT_EQ(value2, kDerivedCtorValue);
- SequenceBound<Other> other = std::move(mderived);
+// Classes with multiple-derived bases may need pointer adjustments when
+// upcasting. These tests rely on sanitizers to catch potential mistakes.
+TYPED_TEST(SequenceBoundTest, MoveConstructionUpcastsToLeftmost) {
+ SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
+ this->background_task_runner_, std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Leftmost> leftmost_base = std::move(multiply_derived);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
+ EXPECT_TRUE(multiply_derived.is_null());
+ EXPECT_FALSE(leftmost_base.is_null());
- other.Reset();
- base::RunLoop().RunUntilIdle();
-
- // Both destructors should have run.
- EXPECT_EQ(value, kOtherDtorValue);
- EXPECT_EQ(value2, kDerivedDtorValue);
+ // The original `MultiplyDerived` object is now owned by
+ // `SequencedBound<Leftmost>`; make sure all the expected destructors
+ // still run when it is reset.
+ leftmost_base.Reset();
+ this->FlushPostedTasks();
+ EXPECT_THAT(
+ this->logger_.TakeEvents(),
+ ::testing::ElementsAre(
+ "constructed Leftmost", "constructed Base", "constructed Rightmost",
+ "constructed MultiplyDerived", "destroyed MultiplyDerived",
+ "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}
-TEST_F(SequenceBoundTest, MultiplyDerivedDestructionWorksRightSuper) {
- // Verify that everything works when we're casting around in ways that might
- // change the address. We cast to the right side of MultiplyDerived and then
- // reset the owner. ASAN will catch free() errors.
- Value value2 = kInitialValue;
- auto mderived = SequenceBound<MultiplyDerived>(task_runner_, &value, &value2);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kOtherCtorValue);
- EXPECT_EQ(value2, kDerivedCtorValue);
- SequenceBound<Base> base = std::move(mderived);
+TYPED_TEST(SequenceBoundTest, MoveConstructionUpcastsToRightmost) {
+ SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
+ this->background_task_runner_, std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Rightmost> rightmost_base = std::move(multiply_derived);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
+ EXPECT_TRUE(multiply_derived.is_null());
+ EXPECT_FALSE(rightmost_base.is_null());
- base.Reset();
- base::RunLoop().RunUntilIdle();
-
- // Both destructors should have run.
- EXPECT_EQ(value, kOtherDtorValue);
- EXPECT_EQ(value2, kDerivedDtorValue);
+ // The original `MultiplyDerived` object is now owned by
+ // `SequencedBound<Rightmost>`; make sure all the expected destructors
+ // still run when it is reset.
+ rightmost_base.Reset();
+ this->FlushPostedTasks();
+ EXPECT_THAT(
+ this->logger_.TakeEvents(),
+ ::testing::ElementsAre(
+ "constructed Leftmost", "constructed Base", "constructed Rightmost",
+ "constructed MultiplyDerived", "destroyed MultiplyDerived",
+ "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}
-TEST_F(SequenceBoundTest, MoveAssignmentFromSameClass) {
- // Test move-assignment using the same classes.
- auto derived_old = SequenceBound<Derived>(task_runner_, &value);
- SequenceBound<Derived> derived_new;
+TYPED_TEST(SequenceBoundTest, MoveAssignment) {
+ SEQUENCE_BOUND_T<Derived> derived_old(this->background_task_runner_,
+ std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Derived> derived_new;
derived_new = std::move(derived_old);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(derived_old.is_null());
EXPECT_FALSE(derived_new.is_null());
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedCtorValue);
- // Verify that |derived_new| owns the object now. Also verifies that move
- // assignment from the same class deletes the outgoing object.
- derived_new = SequenceBound<Derived>();
- EXPECT_EQ(value, kDerivedCtorValue);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedDtorValue);
+ // Note that this explicitly avoids using `Reset()` as a basic test that
+ // assignment resets any previously-owned object.
+ derived_new = SEQUENCE_BOUND_T<Derived>();
+ this->FlushPostedTasks();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed Base", "constructed Derived",
+ "destroyed Derived", "destroyed Base"));
}
-TEST_F(SequenceBoundTest, MoveAssignmentFromDerivedClass) {
- // Move-assignment from a derived class to a base class.
- auto derived = SequenceBound<Derived>(task_runner_, &value);
- SequenceBound<Base> base;
+TYPED_TEST(SequenceBoundTest, MoveAssignmentUpcastsToBase) {
+ SEQUENCE_BOUND_T<Derived> derived(this->background_task_runner_,
+ std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Base> base;
base = std::move(derived);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
EXPECT_TRUE(derived.is_null());
EXPECT_FALSE(base.is_null());
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedCtorValue);
- // Verify that |base| owns the object now, and that destruction still destroys
- // Derived properly.
+ // The original `Derived` object is now owned by `SequencedBound<Base>`; make
+ // sure `~Derived()` still runs when it is reset.
base.Reset();
- EXPECT_EQ(value, kDerivedCtorValue);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedDtorValue);
+ this->FlushPostedTasks();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed Base", "constructed Derived",
+ "destroyed Derived", "destroyed Base"));
}
-TEST_F(SequenceBoundTest, MoveAssignmentFromDerivedClassDestroysOldObject) {
- // Verify that move-assignment from a derived class runs the dtor of the
- // outgoing object.
- auto derived = SequenceBound<Derived>(task_runner_, &value);
+TYPED_TEST(SequenceBoundTest, MoveAssignmentUpcastsToLeftmost) {
+ SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
+ this->background_task_runner_, std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Leftmost> leftmost_base;
- Value value1 = kInitialValue;
- Value value2 = kInitialValue;
- auto mderived =
- SequenceBound<MultiplyDerived>(task_runner_, &value1, &value2);
- base::RunLoop().RunUntilIdle();
+ leftmost_base = std::move(multiply_derived);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
+ EXPECT_TRUE(multiply_derived.is_null());
+ EXPECT_FALSE(leftmost_base.is_null());
- EXPECT_EQ(value, kDerivedCtorValue);
-
- // Assign |mderived|, and verify that the original object in |derived| is
- // destroyed properly.
- derived = std::move(mderived);
- base::RunLoop().RunUntilIdle();
- EXPECT_EQ(value, kDerivedDtorValue);
-
- // Delete |derived|, since it has pointers to local vars.
- derived.Reset();
- base::RunLoop().RunUntilIdle();
+ // The original `MultiplyDerived` object is now owned by
+ // `SequencedBound<Leftmost>`; make sure all the expected destructors
+ // still run when it is reset.
+ leftmost_base.Reset();
+ this->FlushPostedTasks();
+ EXPECT_THAT(
+ this->logger_.TakeEvents(),
+ ::testing::ElementsAre(
+ "constructed Leftmost", "constructed Base", "constructed Rightmost",
+ "constructed MultiplyDerived", "destroyed MultiplyDerived",
+ "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}
-TEST_F(SequenceBoundTest, MultiplyDerivedPostToLeftBaseClass) {
- // Cast and call methods on the left base class.
- Value value1 = kInitialValue;
- Value value2 = kInitialValue;
- auto mderived =
- SequenceBound<MultiplyDerived>(task_runner_, &value1, &value2);
+TYPED_TEST(SequenceBoundTest, MoveAssignmentUpcastsToRightmost) {
+ SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
+ this->background_task_runner_, std::ref(this->logger_));
+ SEQUENCE_BOUND_T<Rightmost> rightmost_base;
- // Cast to Other, the left base.
- SequenceBound<Other> other(std::move(mderived));
- other.Post(FROM_HERE, &Other::SetValue, kDifferentValue);
+ rightmost_base = std::move(multiply_derived);
+ // NOLINTNEXTLINE(bugprone-use-after-move)
+ EXPECT_TRUE(multiply_derived.is_null());
+ EXPECT_FALSE(rightmost_base.is_null());
- base::RunLoop().RunUntilIdle();
-
- EXPECT_EQ(value1, kDifferentValue);
- EXPECT_EQ(value2, kDerivedCtorValue);
-
- other.Reset();
- base::RunLoop().RunUntilIdle();
+ // The original `MultiplyDerived` object is now owned by
+ // `SequencedBound<Rightmost>`; make sure all the expected destructors
+ // still run when it is reset.
+ rightmost_base.Reset();
+ this->FlushPostedTasks();
+ EXPECT_THAT(
+ this->logger_.TakeEvents(),
+ ::testing::ElementsAre(
+ "constructed Leftmost", "constructed Base", "constructed Rightmost",
+ "constructed MultiplyDerived", "destroyed MultiplyDerived",
+ "destroyed Rightmost", "destroyed Base", "destroyed Leftmost"));
}
-TEST_F(SequenceBoundTest, MultiplyDerivedPostToRightBaseClass) {
- // Cast and call methods on the right base class.
- Value value1 = kInitialValue;
- Value value2 = kInitialValue;
- auto mderived =
- SequenceBound<MultiplyDerived>(task_runner_, &value1, &value2);
-
- SequenceBound<Derived> derived(std::move(mderived));
- derived.Post(FROM_HERE, &Derived::SetValue, kDifferentValue);
-
- base::RunLoop().RunUntilIdle();
-
- EXPECT_EQ(value1, kOtherCtorValue);
- EXPECT_EQ(value2, kDifferentValue);
-
- derived.Reset();
- base::RunLoop().RunUntilIdle();
+TYPED_TEST(SequenceBoundTest, AsyncCallLeftmost) {
+ SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
+ this->background_task_runner_, std::ref(this->logger_));
+ multiply_derived.AsyncCall(&Leftmost::SetValue).WithArgs(3);
+ multiply_derived.FlushPostedTasksForTesting();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed Leftmost", "constructed Base",
+ "constructed Rightmost",
+ "constructed MultiplyDerived",
+ "set Leftmost to 3"));
}
-TEST_F(SequenceBoundTest, MoveConstructionFromNullWorks) {
- // Verify that this doesn't crash.
- SequenceBound<Derived> derived1;
- SequenceBound<Derived> derived2(std::move(derived1));
+TYPED_TEST(SequenceBoundTest, AsyncCallRightmost) {
+ SEQUENCE_BOUND_T<MultiplyDerived> multiply_derived(
+ this->background_task_runner_, std::ref(this->logger_));
+ multiply_derived.AsyncCall(&Rightmost::SetValue).WithArgs(3);
+ multiply_derived.FlushPostedTasksForTesting();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed Leftmost", "constructed Base",
+ "constructed Rightmost",
+ "constructed MultiplyDerived",
+ "set Rightmost to 3"));
}
-TEST_F(SequenceBoundTest, MoveAssignmentFromNullWorks) {
- // Verify that this doesn't crash.
- SequenceBound<Derived> derived1;
- SequenceBound<Derived> derived2;
- derived2 = std::move(derived1);
+TYPED_TEST(SequenceBoundTest, MoveConstructionFromNull) {
+ SEQUENCE_BOUND_T<BoxedValue> value1;
+ // Should not crash.
+ SEQUENCE_BOUND_T<BoxedValue> value2(std::move(value1));
}
-TEST_F(SequenceBoundTest, ResetOnNullObjectWorks) {
- // Verify that this doesn't crash.
- SequenceBound<Derived> derived;
- derived.Reset();
+TYPED_TEST(SequenceBoundTest, MoveAssignmentFromNull) {
+ SEQUENCE_BOUND_T<BoxedValue> value1;
+ SEQUENCE_BOUND_T<BoxedValue> value2;
+ // Should not crash.
+ value2 = std::move(value1);
}
-TEST_F(SequenceBoundTest, IsVirtualBaseClassOf) {
- // Check that is_virtual_base_of<> works properly.
-
- // Neither |Base| nor |Derived| is a virtual base of the other.
- static_assert(!internal::is_virtual_base_of<Base, Derived>::value,
- "|Base| shouldn't be a virtual base of |Derived|");
- static_assert(!internal::is_virtual_base_of<Derived, Base>::value,
- "|Derived| shouldn't be a virtual base of |Base|");
-
- // |Base| should be a virtual base class of |VirtuallyDerived|, but not the
- // other way.
- static_assert(internal::is_virtual_base_of<Base, VirtuallyDerived>::value,
- "|Base| should be a virtual base of |VirtuallyDerived|");
- static_assert(!internal::is_virtual_base_of<VirtuallyDerived, Base>::value,
- "|VirtuallyDerived shouldn't be a virtual base of |Base|");
+TYPED_TEST(SequenceBoundTest, MoveAssignmentFromSelf) {
+ SEQUENCE_BOUND_T<BoxedValue> value;
+ // Cheat to avoid clang self-move warning.
+ auto& value2 = value;
+ // Should not crash.
+ value2 = std::move(value);
}
+TYPED_TEST(SequenceBoundTest, ResetNullSequenceBound) {
+ SEQUENCE_BOUND_T<BoxedValue> value;
+ // Should not crash.
+ value.Reset();
+}
+
+TYPED_TEST(SequenceBoundTest, ConstructWithLvalue) {
+ int lvalue = 99;
+ SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, lvalue,
+ &this->logger_);
+ value.FlushPostedTasksForTesting();
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed BoxedValue = 99"));
+}
+
+TYPED_TEST(SequenceBoundTest, PostTaskWithThisObject) {
+ constexpr int kTestValue1 = 42;
+ constexpr int kTestValue2 = 42;
+ SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_,
+ kTestValue1);
+ value.PostTaskWithThisObject(BindLambdaForTesting(
+ [&](const BoxedValue& v) { EXPECT_EQ(kTestValue1, v.value()); }));
+ value.PostTaskWithThisObject(
+ BindLambdaForTesting([&](BoxedValue* v) { v->set_value(kTestValue2); }));
+ value.PostTaskWithThisObject(BindLambdaForTesting(
+ [&](const BoxedValue& v) { EXPECT_EQ(kTestValue2, v.value()); }));
+ value.FlushPostedTasksForTesting();
+}
+
+TYPED_TEST(SequenceBoundTest, SynchronouslyResetForTest) {
+ SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0);
+
+ bool destroyed = false;
+ value.AsyncCall(&BoxedValue::set_destruction_callback)
+ .WithArgs(BindLambdaForTesting([&] { destroyed = true; }));
+
+ value.SynchronouslyResetForTest();
+ EXPECT_TRUE(destroyed);
+}
+
+TYPED_TEST(SequenceBoundTest, FlushPostedTasksForTesting) {
+ SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 0,
+ &this->logger_);
+
+ value.AsyncCall(&BoxedValue::set_value).WithArgs(42);
+ value.FlushPostedTasksForTesting();
+
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre("constructed BoxedValue = 0",
+ "updated BoxedValue from 0 to 42"));
+}
+
+TYPED_TEST(SequenceBoundTest, SmallObject) {
+ class EmptyClass {};
+ SEQUENCE_BOUND_T<EmptyClass> value(this->background_task_runner_);
+ // Test passes if SequenceBound constructor does not crash in AlignedAlloc().
+}
+
+TYPED_TEST(SequenceBoundTest, SelfMoveAssign) {
+ class EmptyClass {};
+ SEQUENCE_BOUND_T<EmptyClass> value(this->background_task_runner_);
+ EXPECT_FALSE(value.is_null());
+ // Clang has a warning for self-move, so be clever.
+ auto& actually_the_same_value = value;
+ value = std::move(actually_the_same_value);
+ // Note: in general, moved-from objects are in a valid but undefined state.
+ // This is merely a test that self-move doesn't result in something bad
+ // happening; this is not an assertion that self-move will always have this
+ // behavior.
+ EXPECT_TRUE(value.is_null());
+}
+
+TYPED_TEST(SequenceBoundTest, Emplace) {
+ SEQUENCE_BOUND_T<BoxedValue> value;
+ EXPECT_TRUE(value.is_null());
+ value.WrappedEmplace(this->background_task_runner_, 8);
+ value.AsyncCall(&BoxedValue::value)
+ .Then(BindLambdaForTesting(
+ [&](int actual_value) { EXPECT_EQ(8, actual_value); }));
+ value.FlushPostedTasksForTesting();
+}
+
+TYPED_TEST(SequenceBoundTest, EmplaceOverExisting) {
+ SEQUENCE_BOUND_T<BoxedValue> value(this->background_task_runner_, 8,
+ &this->logger_);
+ EXPECT_FALSE(value.is_null());
+ value.WrappedEmplace(this->background_task_runner_, 9, &this->logger_);
+ value.AsyncCall(&BoxedValue::value)
+ .Then(BindLambdaForTesting(
+ [&](int actual_value) { EXPECT_EQ(9, actual_value); }));
+ value.FlushPostedTasksForTesting();
+
+ if constexpr (TypeParam::kManagingTaskRunnerConstructsT) {
+ // Both the replaced `BoxedValue` and the current `BoxedValue` should
+ // live on the same sequence: make sure the replaced `BoxedValue` was
+ // destroyed before the current `BoxedValue` was constructed.
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre(
+ "constructed BoxedValue = 8", "destroyed BoxedValue = 8",
+ "constructed BoxedValue = 9", "accessed BoxedValue = 9"));
+ } else {
+ // When `SequenceBound` manages a `std::unique_ptr<T>`, `T` is constructed
+ // on the current sequence so construction of the new managed instance will
+ // happen before the previously-managed instance is destroyed on the
+ // managing task runner.
+ EXPECT_THAT(this->logger_.TakeEvents(),
+ ::testing::ElementsAre(
+ "constructed BoxedValue = 8", "constructed BoxedValue = 9",
+ "destroyed BoxedValue = 8", "accessed BoxedValue = 9"));
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, EmplaceOverExistingWithTaskRunnerSwap) {
+ scoped_refptr<SequencedTaskRunner> another_task_runner =
+ ThreadPool::CreateSequencedTaskRunner({});
+ // No `EventLogger` here since destruction of the old `BoxedValue` and
+ // construction of the new `BoxedValue` take place on different sequences and
+ // can arbitrarily race.
+ SEQUENCE_BOUND_T<BoxedValue> value(another_task_runner, 8);
+ EXPECT_FALSE(value.is_null());
+ value.WrappedEmplace(this->background_task_runner_, 9);
+ {
+ value.PostTaskWithThisObject(BindLambdaForTesting(
+ [another_task_runner,
+ background_task_runner =
+ this->background_task_runner_](const BoxedValue& boxed_value) {
+ EXPECT_FALSE(another_task_runner->RunsTasksInCurrentSequence());
+ EXPECT_TRUE(background_task_runner->RunsTasksInCurrentSequence());
+ EXPECT_EQ(9, boxed_value.value());
+ }));
+ value.FlushPostedTasksForTesting();
+ }
+}
+
+namespace {
+
+class NoArgsVoidReturn {
+ public:
+ void Method() {
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ }
+ void ConstMethod() const {
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ }
+
+ void set_loop(RunLoop* loop) { loop_ = loop; }
+
+ private:
+ mutable raw_ptr<RunLoop> loop_ = nullptr;
+};
+
+class NoArgsIntReturn {
+ public:
+ int Method() { return 123; }
+ int ConstMethod() const { return 456; }
+};
+
+class IntArgVoidReturn {
+ public:
+ IntArgVoidReturn(int* method_called_with, int* const_method_called_with)
+ : method_called_with_(method_called_with),
+ const_method_called_with_(const_method_called_with) {}
+
+ void Method(int x) {
+ *method_called_with_ = x;
+ method_called_with_ = nullptr;
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ }
+ void ConstMethod(int x) const {
+ *const_method_called_with_ = x;
+ const_method_called_with_ = nullptr;
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ }
+
+ void set_loop(RunLoop* loop) { loop_ = loop; }
+
+ private:
+ raw_ptr<int> method_called_with_;
+ mutable raw_ptr<int> const_method_called_with_;
+ mutable raw_ptr<RunLoop> loop_ = nullptr;
+};
+
+class IntArgIntReturn {
+ public:
+ int Method(int x) { return -x; }
+ int ConstMethod(int x) const { return -x; }
+};
+
+} // namespace
+
+TYPED_TEST(SequenceBoundTest, AsyncCallNoArgsNoThen) {
+ SEQUENCE_BOUND_T<NoArgsVoidReturn> s(this->background_task_runner_);
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
+ s.AsyncCall(&NoArgsVoidReturn::Method);
+ loop.Run();
+ }
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
+ s.AsyncCall(&NoArgsVoidReturn::ConstMethod);
+ loop.Run();
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallIntArgNoThen) {
+ int method_called_with = 0;
+ int const_method_called_with = 0;
+ SEQUENCE_BOUND_T<IntArgVoidReturn> s(this->background_task_runner_,
+ &method_called_with,
+ &const_method_called_with);
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
+ s.AsyncCall(&IntArgVoidReturn::Method).WithArgs(123);
+ loop.Run();
+ EXPECT_EQ(123, method_called_with);
+ }
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
+ s.AsyncCall(&IntArgVoidReturn::ConstMethod).WithArgs(456);
+ loop.Run();
+ EXPECT_EQ(456, const_method_called_with);
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallNoArgsVoidThen) {
+ SEQUENCE_BOUND_T<NoArgsVoidReturn> s(this->background_task_runner_);
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&NoArgsVoidReturn::Method).Then(BindLambdaForTesting([&]() {
+ loop.Quit();
+ }));
+ loop.Run();
+ }
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&NoArgsVoidReturn::ConstMethod)
+ .Then(BindLambdaForTesting([&]() { loop.Quit(); }));
+ loop.Run();
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallNoArgsIntThen) {
+ SEQUENCE_BOUND_T<NoArgsIntReturn> s(this->background_task_runner_);
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&NoArgsIntReturn::Method)
+ .Then(BindLambdaForTesting([&](int result) {
+ EXPECT_EQ(123, result);
+ loop.Quit();
+ }));
+ loop.Run();
+ }
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&NoArgsIntReturn::ConstMethod)
+ .Then(BindLambdaForTesting([&](int result) {
+ EXPECT_EQ(456, result);
+ loop.Quit();
+ }));
+ loop.Run();
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallWithArgsVoidThen) {
+ int method_called_with = 0;
+ int const_method_called_with = 0;
+ SEQUENCE_BOUND_T<IntArgVoidReturn> s(this->background_task_runner_,
+ &method_called_with,
+ &const_method_called_with);
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&IntArgVoidReturn::Method)
+ .WithArgs(123)
+ .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+ loop.Run();
+ EXPECT_EQ(123, method_called_with);
+ }
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&IntArgVoidReturn::ConstMethod)
+ .WithArgs(456)
+ .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+ loop.Run();
+ EXPECT_EQ(456, const_method_called_with);
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallWithArgsIntThen) {
+ SEQUENCE_BOUND_T<IntArgIntReturn> s(this->background_task_runner_);
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&IntArgIntReturn::Method)
+ .WithArgs(123)
+ .Then(BindLambdaForTesting([&](int result) {
+ EXPECT_EQ(-123, result);
+ loop.Quit();
+ }));
+ loop.Run();
+ }
+
+ {
+ RunLoop loop;
+ s.AsyncCall(&IntArgIntReturn::ConstMethod)
+ .WithArgs(456)
+ .Then(BindLambdaForTesting([&](int result) {
+ EXPECT_EQ(-456, result);
+ loop.Quit();
+ }));
+ loop.Run();
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallIsConstQualified) {
+ // Tests that both const and non-const methods may be called through a
+ // const-qualified SequenceBound.
+ const SEQUENCE_BOUND_T<NoArgsVoidReturn> s(this->background_task_runner_);
+ s.AsyncCall(&NoArgsVoidReturn::ConstMethod);
+ s.AsyncCall(&NoArgsVoidReturn::Method);
+}
+
+class IgnoreResultTestHelperWithNoArgs {
+ public:
+ explicit IgnoreResultTestHelperWithNoArgs(RunLoop* loop, bool* called)
+ : loop_(loop), called_(called) {}
+
+ int ConstMethod() const {
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ if (called_) {
+ *called_ = true;
+ called_ = nullptr;
+ }
+ return 0;
+ }
+
+ int Method() {
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ if (called_) {
+ *called_ = true;
+ called_ = nullptr;
+ }
+ return 0;
+ }
+
+ private:
+ mutable raw_ptr<RunLoop> loop_ = nullptr;
+ mutable raw_ptr<bool> called_ = nullptr;
+};
+
+TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultNoArgs) {
+ {
+ RunLoop loop;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
+ this->background_task_runner_, &loop, nullptr);
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod));
+ loop.Run();
+ }
+
+ {
+ RunLoop loop;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
+ this->background_task_runner_, &loop, nullptr);
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method));
+ loop.Run();
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultThen) {
+ {
+ RunLoop loop;
+ bool called = false;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
+ this->background_task_runner_, nullptr, &called);
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::ConstMethod))
+ .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+ loop.Run();
+ EXPECT_TRUE(called);
+ }
+
+ {
+ RunLoop loop;
+ bool called = false;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithNoArgs> s(
+ this->background_task_runner_, nullptr, &called);
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithNoArgs::Method))
+ .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+ loop.Run();
+ EXPECT_TRUE(called);
+ }
+}
+
+class IgnoreResultTestHelperWithArgs {
+ public:
+ IgnoreResultTestHelperWithArgs(RunLoop* loop, int& value)
+ : loop_(loop), value_(&value) {}
+
+ int ConstMethod(int arg) const {
+ if (value_) {
+ *value_ = arg;
+ value_ = nullptr;
+ }
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ return arg;
+ }
+
+ int Method(int arg) {
+ if (value_) {
+ *value_ = arg;
+ value_ = nullptr;
+ }
+ if (loop_) {
+ loop_->Quit();
+ loop_ = nullptr;
+ }
+ return arg;
+ }
+
+ private:
+ mutable raw_ptr<RunLoop> loop_ = nullptr;
+ mutable raw_ptr<int> value_;
+};
+
+TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultWithArgs) {
+ {
+ RunLoop loop;
+ int result = 0;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
+ this->background_task_runner_, &loop, std::ref(result));
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::ConstMethod))
+ .WithArgs(60);
+ loop.Run();
+ EXPECT_EQ(60, result);
+ }
+
+ {
+ RunLoop loop;
+ int result = 0;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
+ this->background_task_runner_, &loop, std::ref(result));
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::Method))
+ .WithArgs(06);
+ loop.Run();
+ EXPECT_EQ(06, result);
+ }
+}
+
+TYPED_TEST(SequenceBoundTest, AsyncCallIgnoreResultWithArgsThen) {
+ {
+ RunLoop loop;
+ int result = 0;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
+ this->background_task_runner_, nullptr, std::ref(result));
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::ConstMethod))
+ .WithArgs(60)
+ .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+ loop.Run();
+ EXPECT_EQ(60, result);
+ }
+
+ {
+ RunLoop loop;
+ int result = 0;
+ SEQUENCE_BOUND_T<IgnoreResultTestHelperWithArgs> s(
+ this->background_task_runner_, nullptr, std::ref(result));
+ s.AsyncCall(IgnoreResult(&IgnoreResultTestHelperWithArgs::Method))
+ .WithArgs(06)
+ .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+ loop.Run();
+ EXPECT_EQ(06, result);
+ }
+}
+
+// TODO(https://crbug.com/1382549): Maybe use the nocompile harness here instead
+// of being "clever"...
+TYPED_TEST(SequenceBoundTest, NoCompileTests) {
+ // TODO(https://crbug.com/1382549): Test calling WithArgs() on a method that
+ // takes no arguments.
+ //
+ // Given:
+ // class C {
+ // void F();
+ // };
+ //
+ // Then:
+ // SequenceBound<C> s(...);
+ // s.AsyncCall(&C::F).WithArgs(...);
+ //
+ // should not compile.
+ //
+ // TODO(https://crbug.com/1382549): Test calling Then() before calling
+ // WithArgs().
+ //
+ // Given:
+ // class C {
+ // void F(int);
+ // };
+ //
+ // Then:
+ // SequenceBound<C> s(...);
+ // s.AsyncCall(&C::F).Then(...).WithArgs(...);
+ //
+ // should not compile.
+ //
+ // TODO(https://crbug.com/1382549): Add no-compile tests for converting
+ // between SequenceBound<T> and SequenceBound<std::unique_ptr<T>>.
+}
+#undef SequenceBound
+
+class SequenceBoundDeathTest : public ::testing::Test {
+ protected:
+ void TearDown() override {
+ // Make sure that any objects owned by `SequenceBound` have been destroyed
+ // to avoid tripping leak detection.
+ RunLoop run_loop;
+ task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ // Death tests use fork(), which can interact (very) poorly with threads.
+ test::SingleThreadTaskEnvironment task_environment_;
+ scoped_refptr<SequencedTaskRunner> task_runner_ =
+ SequencedTaskRunner::GetCurrentDefault();
+};
+
+TEST_F(SequenceBoundDeathTest, AsyncCallIntArgNoWithArgsShouldCheck) {
+ SequenceBound<IntArgIntReturn> s(task_runner_);
+ EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method), "");
+}
+
+TEST_F(SequenceBoundDeathTest, AsyncCallIntReturnNoThenShouldCheck) {
+ {
+ SequenceBound<NoArgsIntReturn> s(task_runner_);
+ EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&NoArgsIntReturn::Method), "");
+ }
+
+ {
+ SequenceBound<IntArgIntReturn> s(task_runner_);
+ EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method).WithArgs(0),
+ "");
+ }
+}
+
+} // namespace
+
} // namespace base
diff --git a/base/threading/sequence_local_storage_map.cc b/base/threading/sequence_local_storage_map.cc
index 2837aa0..3dcb03d 100644
--- a/base/threading/sequence_local_storage_map.cc
+++ b/base/threading/sequence_local_storage_map.cc
@@ -1,50 +1,78 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/sequence_local_storage_map.h"
+#include <ostream>
#include <utility>
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/threading/thread_local.h"
+#include "base/check_op.h"
+#include "third_party/abseil-cpp/absl/base/attributes.h"
+
+#if defined(STARBOARD)
+#include <pthread.h>
+
+#include "base/check_op.h"
+#include "starboard/thread.h"
+#endif
namespace base {
namespace internal {
namespace {
-LazyInstance<ThreadLocalPointer<SequenceLocalStorageMap>>::Leaky
- tls_current_sequence_local_storage = LAZY_INSTANCE_INITIALIZER;
+
+#if defined(STARBOARD)
+ABSL_CONST_INIT pthread_once_t s_once_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_key = 0;
+
+void InitThreadLocalKey() {
+ int res = pthread_key_create(&s_thread_local_key , NULL);
+ DCHECK(res == 0);
+}
+
+void EnsureThreadLocalKeyInited() {
+ pthread_once(&s_once_flag, InitThreadLocalKey);
+}
+
+SequenceLocalStorageMap* GetCurrentSequenceLocalStorage() {
+ EnsureThreadLocalKeyInited();
+ return static_cast<SequenceLocalStorageMap*>(
+ pthread_getspecific(s_thread_local_key));
+}
+#else
+ABSL_CONST_INIT thread_local SequenceLocalStorageMap*
+ current_sequence_local_storage = nullptr;
+#endif
+
} // namespace
SequenceLocalStorageMap::SequenceLocalStorageMap() = default;
SequenceLocalStorageMap::~SequenceLocalStorageMap() = default;
-ScopedSetSequenceLocalStorageMapForCurrentThread::
- ScopedSetSequenceLocalStorageMapForCurrentThread(
- SequenceLocalStorageMap* sequence_local_storage) {
- DCHECK(!tls_current_sequence_local_storage.Get().Get());
- tls_current_sequence_local_storage.Get().Set(sequence_local_storage);
-}
-
-ScopedSetSequenceLocalStorageMapForCurrentThread::
- ~ScopedSetSequenceLocalStorageMapForCurrentThread() {
- tls_current_sequence_local_storage.Get().Set(nullptr);
-}
-
+// static
SequenceLocalStorageMap& SequenceLocalStorageMap::GetForCurrentThread() {
- SequenceLocalStorageMap* current_sequence_local_storage =
- tls_current_sequence_local_storage.Get().Get();
-
- DCHECK(current_sequence_local_storage)
+ DCHECK(IsSetForCurrentThread())
<< "SequenceLocalStorageSlot cannot be used because no "
"SequenceLocalStorageMap was stored in TLS. Use "
"ScopedSetSequenceLocalStorageMapForCurrentThread to store a "
"SequenceLocalStorageMap object in TLS.";
+#if defined(STARBOARD)
+ return *GetCurrentSequenceLocalStorage();
+#else
return *current_sequence_local_storage;
+#endif
+}
+
+// static
+bool SequenceLocalStorageMap::IsSetForCurrentThread() {
+#if defined(STARBOARD)
+ return GetCurrentSequenceLocalStorage() != nullptr;
+#else
+ return current_sequence_local_storage != nullptr;
+#endif
}
void* SequenceLocalStorageMap::Get(int slot_id) {
@@ -101,5 +129,30 @@
return *this;
}
+ScopedSetSequenceLocalStorageMapForCurrentThread::
+ ScopedSetSequenceLocalStorageMapForCurrentThread(
+ SequenceLocalStorageMap* sequence_local_storage)
+#if defined(STARBOARD)
+{
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, sequence_local_storage);
+}
+#else
+ : resetter_(¤t_sequence_local_storage,
+ sequence_local_storage,
+ nullptr) {}
+#endif
+
+#if defined(STARBOARD)
+ScopedSetSequenceLocalStorageMapForCurrentThread::
+ ~ScopedSetSequenceLocalStorageMapForCurrentThread() {
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, nullptr);
+}
+#else
+ScopedSetSequenceLocalStorageMapForCurrentThread::
+ ~ScopedSetSequenceLocalStorageMapForCurrentThread() = default;
+#endif
+
} // namespace internal
} // namespace base
diff --git a/base/threading/sequence_local_storage_map.h b/base/threading/sequence_local_storage_map.h
index 8b9155c..64e1bf0 100644
--- a/base/threading/sequence_local_storage_map.h
+++ b/base/threading/sequence_local_storage_map.h
@@ -1,13 +1,14 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_SEQUENCE_LOCAL_STORAGE_MAP_H_
#define BASE_THREADING_SEQUENCE_LOCAL_STORAGE_MAP_H_
+#include "base/auto_reset.h"
#include "base/base_export.h"
#include "base/containers/flat_map.h"
-#include "base/macros.h"
+#include "base/memory/raw_ptr_exclusion.h"
namespace base {
namespace internal {
@@ -15,7 +16,7 @@
// A SequenceLocalStorageMap holds (slot_id) -> (value, destructor) items for a
// sequence. When a task runs, it is expected that a pointer to its sequence's
// SequenceLocalStorageMap is set in TLS using
-// ScopedSetSequenceMapLocalStorageForCurrentThread. When a
+// ScopedSetSequenceLocalStorageMapForCurrentThread. When a
// SequenceLocalStorageMap is destroyed, it invokes the destructors associated
// with values stored within it.
// The Get() and Set() methods should not be accessed directly.
@@ -24,6 +25,10 @@
class BASE_EXPORT SequenceLocalStorageMap {
public:
SequenceLocalStorageMap();
+
+ SequenceLocalStorageMap(const SequenceLocalStorageMap&) = delete;
+ SequenceLocalStorageMap& operator=(const SequenceLocalStorageMap&) = delete;
+
~SequenceLocalStorageMap();
// Returns the SequenceLocalStorage bound to the current thread. It is invalid
@@ -31,6 +36,11 @@
// ScopedSetSequenceLocalStorageForCurrentThread.
static SequenceLocalStorageMap& GetForCurrentThread();
+ // Indicates whether the current thread has a SequenceLocalStorageMap
+ // available and thus whether it can safely call GetForCurrentThread and
+ // dereference SequenceLocalStorageSlots.
+ static bool IsSetForCurrentThread();
+
// Holds a pointer to a value alongside a destructor for this pointer.
// Calls the destructor on the value upon destruction.
class BASE_EXPORT ValueDestructorPair {
@@ -38,6 +48,10 @@
using DestructorFunc = void(void*);
ValueDestructorPair(void* value, DestructorFunc* destructor);
+
+ ValueDestructorPair(const ValueDestructorPair&) = delete;
+ ValueDestructorPair& operator=(const ValueDestructorPair&) = delete;
+
~ValueDestructorPair();
ValueDestructorPair(ValueDestructorPair&& value_destructor_pair);
@@ -47,10 +61,10 @@
void* value() const { return value_; }
private:
- void* value_;
- DestructorFunc* destructor_;
-
- DISALLOW_COPY_AND_ASSIGN(ValueDestructorPair);
+ // `value_` and `destructor_` are not a raw_ptr<...> for performance reasons
+ // (based on analysis of sampling profiler data and tab_search:top100:2020).
+ RAW_PTR_EXCLUSION void* value_;
+ RAW_PTR_EXCLUSION DestructorFunc* destructor_;
};
// Returns the value stored in |slot_id| or nullptr if no value was stored.
@@ -66,23 +80,30 @@
// in the map. For low number of entries, flat_map is known to perform better
// than other map implementations.
base::flat_map<int, ValueDestructorPair> sls_map_;
-
- DISALLOW_COPY_AND_ASSIGN(SequenceLocalStorageMap);
};
// Within the scope of this object,
// SequenceLocalStorageMap::GetForCurrentThread() will return a reference to the
// SequenceLocalStorageMap object passed to the constructor. There can be only
// one ScopedSetSequenceLocalStorageMapForCurrentThread instance per scope.
-class BASE_EXPORT ScopedSetSequenceLocalStorageMapForCurrentThread {
+class BASE_EXPORT
+ [[maybe_unused,
+ nodiscard]] ScopedSetSequenceLocalStorageMapForCurrentThread {
public:
ScopedSetSequenceLocalStorageMapForCurrentThread(
SequenceLocalStorageMap* sequence_local_storage);
+ ScopedSetSequenceLocalStorageMapForCurrentThread(
+ const ScopedSetSequenceLocalStorageMapForCurrentThread&) = delete;
+ ScopedSetSequenceLocalStorageMapForCurrentThread& operator=(
+ const ScopedSetSequenceLocalStorageMapForCurrentThread&) = delete;
+
~ScopedSetSequenceLocalStorageMapForCurrentThread();
+#if !defined(STARBOARD)
private:
- DISALLOW_COPY_AND_ASSIGN(ScopedSetSequenceLocalStorageMapForCurrentThread);
+ const base::AutoReset<SequenceLocalStorageMap*> resetter_;
+#endif
};
} // namespace internal
} // namespace base
diff --git a/base/threading/sequence_local_storage_map_unittest.cc b/base/threading/sequence_local_storage_map_unittest.cc
index a45bbc3..4869427 100644
--- a/base/threading/sequence_local_storage_map_unittest.cc
+++ b/base/threading/sequence_local_storage_map_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -7,6 +7,7 @@
#include <memory>
#include <utility>
+#include "base/memory/raw_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -23,15 +24,17 @@
DCHECK(was_destroyed_ptr_);
DCHECK(!(*was_destroyed_ptr_));
}
+
+ SetOnDestroy(const SetOnDestroy&) = delete;
+ SetOnDestroy& operator=(const SetOnDestroy&) = delete;
+
~SetOnDestroy() {
DCHECK(!(*was_destroyed_ptr_));
*was_destroyed_ptr_ = true;
}
private:
- bool* const was_destroyed_ptr_;
-
- DISALLOW_COPY_AND_ASSIGN(SetOnDestroy);
+ const raw_ptr<bool> was_destroyed_ptr_;
};
template <typename T, typename... Args>
diff --git a/base/threading/sequence_local_storage_slot.cc b/base/threading/sequence_local_storage_slot.cc
index b7db40b..8dc49c9 100644
--- a/base/threading/sequence_local_storage_slot.cc
+++ b/base/threading/sequence_local_storage_slot.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -7,7 +7,7 @@
#include <limits>
#include "base/atomic_sequence_num.h"
-#include "base/logging.h"
+#include "base/check_op.h"
namespace base {
namespace internal {
@@ -23,4 +23,5 @@
}
} // namespace internal
+
} // namespace base
diff --git a/base/threading/sequence_local_storage_slot.h b/base/threading/sequence_local_storage_slot.h
index 315df7d..12c04bb 100644
--- a/base/threading/sequence_local_storage_slot.h
+++ b/base/threading/sequence_local_storage_slot.h
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -9,6 +9,7 @@
#include <utility>
#include "base/base_export.h"
+#include "base/template_util.h"
#include "base/threading/sequence_local_storage_map.h"
namespace base {
@@ -22,17 +23,18 @@
//
// Example usage:
//
-// namespace {
-// base::LazyInstance<SequenceLocalStorageSlot<int>> sls_value;
+// int& GetSequenceLocalStorage()
+// static base::NoDestructor<SequenceLocalStorageSlot<int>> sls_value;
+// return sls_value->GetOrCreateValue();
// }
//
// void Read() {
-// int value = sls_value.Get().Get();
+// int value = GetSequenceLocalStorage();
// ...
// }
//
// void Write() {
-// sls_value.Get().Set(42);
+// GetSequenceLocalStorage() = 42;
// }
//
// void PostTasks() {
@@ -46,36 +48,61 @@
//
// SequenceLocalStorageSlot must be used within the scope of a
// ScopedSetSequenceLocalStorageMapForCurrentThread object.
-// Note: this is true on all TaskScheduler workers and on threads bound to a
+// Note: this is true on all ThreadPool workers and on threads bound to a
// MessageLoop.
template <typename T, typename Deleter = std::default_delete<T>>
class SequenceLocalStorageSlot {
public:
SequenceLocalStorageSlot()
: slot_id_(internal::GetNextSequenceLocalStorageSlotNumber()) {}
+
+ SequenceLocalStorageSlot(const SequenceLocalStorageSlot&) = delete;
+ SequenceLocalStorageSlot& operator=(const SequenceLocalStorageSlot&) = delete;
+
~SequenceLocalStorageSlot() = default;
- // Get the sequence-local value stored in this slot. Returns a
- // default-constructed value if no value was previously set.
- T& Get() {
- void* value =
- internal::SequenceLocalStorageMap::GetForCurrentThread().Get(slot_id_);
+ operator bool() const { return GetValuePointer() != nullptr; }
- // Sets and returns a default-constructed value if no value was previously
- // set.
- if (!value) {
- Set(T());
- return Get();
- }
- return *(static_cast<T*>(value));
+ // Default-constructs the value for the current sequence if not
+ // already constructed. Then, returns the value.
+ T& GetOrCreateValue() {
+ T* ptr = GetValuePointer();
+ if (!ptr)
+ ptr = emplace();
+ return *ptr;
}
- // Set this slot's sequence-local value to |value|.
- // Note that if T is expensive to copy, it may be more appropriate to instead
- // store a std::unique_ptr<T>. This is enforced by the
- // DISALLOW_COPY_AND_ASSIGN style rather than directly by this class however.
- void Set(T value) {
- // Allocates the |value| with new rather than std::make_unique.
+ // Returns a pointer to the value for the current sequence. May be
+ // nullptr if the value was not constructed on the current sequence.
+ T* GetValuePointer() {
+ void* ptr =
+ internal::SequenceLocalStorageMap::GetForCurrentThread().Get(slot_id_);
+ return static_cast<T*>(ptr);
+ }
+ const T* GetValuePointer() const {
+ return const_cast<SequenceLocalStorageSlot*>(this)->GetValuePointer();
+ }
+
+ T* operator->() { return GetValuePointer(); }
+ const T* operator->() const { return GetValuePointer(); }
+
+ T& operator*() { return *GetValuePointer(); }
+ const T& operator*() const { return *GetValuePointer(); }
+
+ void reset() { Adopt(nullptr); }
+
+ // Constructs this slot's sequence-local value with |args...| and returns a
+ // pointer to the created object.
+ template <class... Args>
+ T* emplace(Args&&... args) {
+ T* value_ptr = new T(std::forward<Args>(args)...);
+ Adopt(value_ptr);
+ return value_ptr;
+ }
+
+ private:
+ // Takes ownership of |value_ptr|.
+ void Adopt(T* value_ptr) {
// Since SequenceLocalStorageMap needs to store values of various types
// within the same map, the type of value_destructor_pair.value is void*
// (std::unique_ptr<void> is invalid). Memory is freed by calling
@@ -83,8 +110,6 @@
// ValueDestructorPair which is invoked when the value is overwritten by
// another call to SequenceLocalStorageMap::Set or when the
// SequenceLocalStorageMap is deleted.
- T* value_ptr = new T(std::move(value));
-
internal::SequenceLocalStorageMap::ValueDestructorPair::DestructorFunc*
destructor = [](void* ptr) { Deleter()(static_cast<T*>(ptr)); };
@@ -95,10 +120,8 @@
slot_id_, std::move(value_destructor_pair));
}
- private:
// |slot_id_| is used as a key in SequenceLocalStorageMap
const int slot_id_;
- DISALLOW_COPY_AND_ASSIGN(SequenceLocalStorageSlot);
};
} // namespace base
diff --git a/base/threading/sequence_local_storage_slot_unittest.cc b/base/threading/sequence_local_storage_slot_unittest.cc
index 4a9f6a9..982c59c 100644
--- a/base/threading/sequence_local_storage_slot_unittest.cc
+++ b/base/threading/sequence_local_storage_slot_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,7 +6,6 @@
#include <utility>
-#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/threading/sequence_local_storage_map.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -16,6 +15,11 @@
namespace {
class SequenceLocalStorageSlotTest : public testing::Test {
+ public:
+ SequenceLocalStorageSlotTest(const SequenceLocalStorageSlotTest&) = delete;
+ SequenceLocalStorageSlotTest& operator=(const SequenceLocalStorageSlotTest&) =
+ delete;
+
protected:
SequenceLocalStorageSlotTest()
: scoped_sequence_local_storage_(&sequence_local_storage_) {}
@@ -23,77 +27,80 @@
internal::SequenceLocalStorageMap sequence_local_storage_;
internal::ScopedSetSequenceLocalStorageMapForCurrentThread
scoped_sequence_local_storage_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(SequenceLocalStorageSlotTest);
};
} // namespace
// Verify that a value stored with Set() can be retrieved with Get().
-TEST_F(SequenceLocalStorageSlotTest, GetSet) {
+TEST_F(SequenceLocalStorageSlotTest, GetEmplace) {
SequenceLocalStorageSlot<int> slot;
- slot.Set(5);
- EXPECT_EQ(slot.Get(), 5);
+ slot.emplace(5);
+ EXPECT_EQ(*slot, 5);
}
-// Verify that setting an object in a SequenceLocalStorageSlot creates a copy
+// Verify that inserting an object in a SequenceLocalStorageSlot creates a copy
// of that object independent of the original one.
-TEST_F(SequenceLocalStorageSlotTest, SetObjectIsIndependent) {
+TEST_F(SequenceLocalStorageSlotTest, EmplaceObjectIsIndependent) {
bool should_be_false = false;
SequenceLocalStorageSlot<bool> slot;
- slot.Set(should_be_false);
+ slot.emplace(should_be_false);
- EXPECT_FALSE(slot.Get());
- slot.Get() = true;
- EXPECT_TRUE(slot.Get());
+ EXPECT_FALSE(*slot);
+ *slot = true;
+ EXPECT_TRUE(*slot);
- EXPECT_NE(should_be_false, slot.Get());
+ EXPECT_NE(should_be_false, *slot);
}
// Verify that multiple slots work and that calling Get after overwriting
// a value in a slot yields the new value.
-TEST_F(SequenceLocalStorageSlotTest, GetSetMultipleSlots) {
+TEST_F(SequenceLocalStorageSlotTest, GetEmplaceMultipleSlots) {
SequenceLocalStorageSlot<int> slot1;
SequenceLocalStorageSlot<int> slot2;
SequenceLocalStorageSlot<int> slot3;
+ EXPECT_FALSE(slot1);
+ EXPECT_FALSE(slot2);
+ EXPECT_FALSE(slot3);
- slot1.Set(1);
- slot2.Set(2);
- slot3.Set(3);
+ slot1.emplace(1);
+ slot2.emplace(2);
+ slot3.emplace(3);
- EXPECT_EQ(slot1.Get(), 1);
- EXPECT_EQ(slot2.Get(), 2);
- EXPECT_EQ(slot3.Get(), 3);
+ EXPECT_TRUE(slot1);
+ EXPECT_TRUE(slot2);
+ EXPECT_TRUE(slot3);
+ EXPECT_EQ(*slot1, 1);
+ EXPECT_EQ(*slot2, 2);
+ EXPECT_EQ(*slot3, 3);
- slot3.Set(4);
- slot2.Set(5);
- slot1.Set(6);
+ slot3.emplace(4);
+ slot2.emplace(5);
+ slot1.emplace(6);
- EXPECT_EQ(slot3.Get(), 4);
- EXPECT_EQ(slot2.Get(), 5);
- EXPECT_EQ(slot1.Get(), 6);
+ EXPECT_EQ(*slot3, 4);
+ EXPECT_EQ(*slot2, 5);
+ EXPECT_EQ(*slot1, 6);
}
-// Verify that changing the the value returned by Get() changes the value
+// Verify that changing the value returned by Get() changes the value
// in sequence local storage.
TEST_F(SequenceLocalStorageSlotTest, GetReferenceModifiable) {
SequenceLocalStorageSlot<bool> slot;
- slot.Set(false);
- slot.Get() = true;
- EXPECT_TRUE(slot.Get());
+ slot.emplace(false);
+ *slot = true;
+ EXPECT_TRUE(*slot);
}
// Verify that a move-only type can be stored in sequence local storage.
-TEST_F(SequenceLocalStorageSlotTest, SetGetWithMoveOnlyType) {
+TEST_F(SequenceLocalStorageSlotTest, EmplaceGetWithMoveOnlyType) {
std::unique_ptr<int> int_unique_ptr = std::make_unique<int>(5);
SequenceLocalStorageSlot<std::unique_ptr<int>> slot;
- slot.Set(std::move(int_unique_ptr));
+ slot.emplace(std::move(int_unique_ptr));
- EXPECT_EQ(*slot.Get(), 5);
+ EXPECT_EQ(*slot->get(), 5);
}
// Verify that a Get() without a previous Set() on a slot returns a
@@ -105,38 +112,38 @@
SequenceLocalStorageSlot<DefaultConstructable> slot;
- EXPECT_EQ(slot.Get().x, 0x12345678);
+ EXPECT_EQ(slot.GetOrCreateValue().x, 0x12345678);
}
-// Verify that a Get() without a previous Set() on a slot with a POD-type
-// returns a default-constructed value.
+// Verify that a GetOrCreateValue() without a previous emplace() on a slot with
+// a POD-type returns a default-constructed value.
// Note: this test could be flaky and give a false pass. If it's flaky, the test
// might've "passed" because the memory for the slot happened to be zeroed.
TEST_F(SequenceLocalStorageSlotTest, GetWithoutSetDefaultConstructsPOD) {
SequenceLocalStorageSlot<void*> slot;
- EXPECT_EQ(slot.Get(), nullptr);
+ EXPECT_EQ(slot.GetOrCreateValue(), nullptr);
}
// Verify that the value of a slot is specific to a SequenceLocalStorageMap
-TEST(SequenceLocalStorageSlotMultipleMapTest, SetGetMultipleMapsOneSlot) {
+TEST(SequenceLocalStorageSlotMultipleMapTest, EmplaceGetMultipleMapsOneSlot) {
SequenceLocalStorageSlot<unsigned int> slot;
internal::SequenceLocalStorageMap sequence_local_storage_maps[5];
// Set the value of the slot to be the index of the current
// SequenceLocalStorageMaps in the vector
- for (unsigned int i = 0; i < arraysize(sequence_local_storage_maps); ++i) {
+ for (unsigned int i = 0; i < std::size(sequence_local_storage_maps); ++i) {
internal::ScopedSetSequenceLocalStorageMapForCurrentThread
scoped_sequence_local_storage(&sequence_local_storage_maps[i]);
- slot.Set(i);
+ slot.emplace(i);
}
- for (unsigned int i = 0; i < arraysize(sequence_local_storage_maps); ++i) {
+ for (unsigned int i = 0; i < std::size(sequence_local_storage_maps); ++i) {
internal::ScopedSetSequenceLocalStorageMapForCurrentThread
scoped_sequence_local_storage(&sequence_local_storage_maps[i]);
- EXPECT_EQ(slot.Get(), i);
+ EXPECT_EQ(*slot, i);
}
}
diff --git a/base/threading/sequenced_task_runner_handle.cc b/base/threading/sequenced_task_runner_handle.cc
deleted file mode 100644
index e6920f5..0000000
--- a/base/threading/sequenced_task_runner_handle.cc
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/threading/sequenced_task_runner_handle.h"
-
-#include <utility>
-
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/threading/thread_local.h"
-#include "base/threading/thread_task_runner_handle.h"
-
-namespace base {
-
-namespace {
-
-LazyInstance<ThreadLocalPointer<SequencedTaskRunnerHandle>>::Leaky
- sequenced_task_runner_tls = LAZY_INSTANCE_INITIALIZER;
-
-} // namespace
-
-// static
-scoped_refptr<SequencedTaskRunner> SequencedTaskRunnerHandle::Get() {
- // Return the registered SequencedTaskRunner, if any.
- const SequencedTaskRunnerHandle* handle =
- sequenced_task_runner_tls.Pointer()->Get();
- if (handle)
- return handle->task_runner_;
-
- // Note if you hit this: the problem is the lack of a sequenced context. The
- // ThreadTaskRunnerHandle is just the last attempt at finding such a context.
- CHECK(ThreadTaskRunnerHandle::IsSet())
- << "Error: This caller requires a sequenced context (i.e. the "
- "current task needs to run from a SequencedTaskRunner).";
- return ThreadTaskRunnerHandle::Get();
-}
-
-// static
-bool SequencedTaskRunnerHandle::IsSet() {
- return sequenced_task_runner_tls.Pointer()->Get() ||
- ThreadTaskRunnerHandle::IsSet();
-}
-
-SequencedTaskRunnerHandle::SequencedTaskRunnerHandle(
- scoped_refptr<SequencedTaskRunner> task_runner)
- : task_runner_(std::move(task_runner)) {
- DCHECK(task_runner_->RunsTasksInCurrentSequence());
- DCHECK(!SequencedTaskRunnerHandle::IsSet());
- sequenced_task_runner_tls.Pointer()->Set(this);
-}
-
-SequencedTaskRunnerHandle::~SequencedTaskRunnerHandle() {
- DCHECK(task_runner_->RunsTasksInCurrentSequence());
- DCHECK_EQ(sequenced_task_runner_tls.Pointer()->Get(), this);
- sequenced_task_runner_tls.Pointer()->Set(nullptr);
-}
-
-} // namespace base
diff --git a/base/threading/sequenced_task_runner_handle.h b/base/threading/sequenced_task_runner_handle.h
deleted file mode 100644
index f55cee5..0000000
--- a/base/threading/sequenced_task_runner_handle.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_THREADING_SEQUENCED_TASK_RUNNER_HANDLE_H_
-#define BASE_THREADING_SEQUENCED_TASK_RUNNER_HANDLE_H_
-
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "base/memory/ref_counted.h"
-#include "base/sequenced_task_runner.h"
-
-namespace base {
-
-class BASE_EXPORT SequencedTaskRunnerHandle {
- public:
- // Returns a SequencedTaskRunner which guarantees that posted tasks will only
- // run after the current task is finished and will satisfy a SequenceChecker.
- // It should only be called if IsSet() returns true (see the comment there for
- // the requirements).
- static scoped_refptr<SequencedTaskRunner> Get();
-
- // Returns true if one of the following conditions is fulfilled:
- // a) A SequencedTaskRunner has been assigned to the current thread by
- // instantiating a SequencedTaskRunnerHandle.
- // b) The current thread has a ThreadTaskRunnerHandle (which includes any
- // thread that has a MessageLoop associated with it).
- static bool IsSet();
-
- // Binds |task_runner| to the current thread.
- explicit SequencedTaskRunnerHandle(
- scoped_refptr<SequencedTaskRunner> task_runner);
- ~SequencedTaskRunnerHandle();
-
- private:
- scoped_refptr<SequencedTaskRunner> task_runner_;
-
- DISALLOW_COPY_AND_ASSIGN(SequencedTaskRunnerHandle);
-};
-
-} // namespace base
-
-#endif // BASE_THREADING_SEQUENCED_TASK_RUNNER_HANDLE_H_
diff --git a/base/threading/sequenced_task_runner_handle_unittest.cc b/base/threading/sequenced_task_runner_handle_unittest.cc
deleted file mode 100644
index b55e1e7..0000000
--- a/base/threading/sequenced_task_runner_handle_unittest.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/threading/sequenced_task_runner_handle.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/location.h"
-#include "base/memory/ref_counted.h"
-#include "base/metrics/statistics_recorder.h"
-#include "base/run_loop.h"
-#include "base/sequence_checker_impl.h"
-#include "base/sequenced_task_runner.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/task/post_task.h"
-#include "base/test/scoped_task_environment.h"
-#include "base/test/test_simple_task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace base {
-namespace {
-
-class SequencedTaskRunnerHandleTest : public ::testing::Test {
- protected:
- SequencedTaskRunnerHandleTest()
- : recorder_for_testing_(StatisticsRecorder::CreateTemporaryForTesting()) {
- }
- // Verifies that the context it runs on has a SequencedTaskRunnerHandle
- // and that posting to it results in the posted task running in that same
- // context (sequence).
- static void VerifyCurrentSequencedTaskRunner() {
- ASSERT_TRUE(SequencedTaskRunnerHandle::IsSet());
- scoped_refptr<SequencedTaskRunner> task_runner =
- SequencedTaskRunnerHandle::Get();
- ASSERT_TRUE(task_runner);
-
- // Use SequenceCheckerImpl to make sure it's not a no-op in Release builds.
- std::unique_ptr<SequenceCheckerImpl> sequence_checker(
- new SequenceCheckerImpl);
- task_runner->PostTask(
- FROM_HERE,
- base::BindOnce(&SequencedTaskRunnerHandleTest::CheckValidSequence,
- std::move(sequence_checker)));
- }
-
- static void CheckValidSequence(
- std::unique_ptr<SequenceCheckerImpl> sequence_checker) {
- EXPECT_TRUE(sequence_checker->CalledOnValidSequence());
- }
-
- std::unique_ptr<StatisticsRecorder> recorder_for_testing_;
- base::test::ScopedTaskEnvironment scoped_task_environment_;
-};
-
-TEST_F(SequencedTaskRunnerHandleTest, FromMessageLoop) {
- VerifyCurrentSequencedTaskRunner();
- RunLoop().RunUntilIdle();
-}
-
-TEST_F(SequencedTaskRunnerHandleTest, FromTaskSchedulerSequencedTask) {
- base::CreateSequencedTaskRunnerWithTraits({})->PostTask(
- FROM_HERE,
- base::BindOnce(
- &SequencedTaskRunnerHandleTest::VerifyCurrentSequencedTaskRunner));
- scoped_task_environment_.RunUntilIdle();
-}
-
-TEST_F(SequencedTaskRunnerHandleTest, NoHandleFromUnsequencedTask) {
- base::PostTask(FROM_HERE, base::BindOnce([]() {
- EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
- }));
- scoped_task_environment_.RunUntilIdle();
-}
-
-TEST(SequencedTaskRunnerHandleTestWithoutMessageLoop, FromHandleInScope) {
- scoped_refptr<SequencedTaskRunner> test_task_runner(new TestSimpleTaskRunner);
- EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
- {
- SequencedTaskRunnerHandle handle(test_task_runner);
- EXPECT_TRUE(SequencedTaskRunnerHandle::IsSet());
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(test_task_runner, SequencedTaskRunnerHandle::Get());
- }
- EXPECT_FALSE(SequencedTaskRunnerHandle::IsSet());
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-}
-
-} // namespace
-} // namespace base
diff --git a/base/threading/simple_thread.cc b/base/threading/simple_thread.cc
index 4b26062..579431f 100644
--- a/base/threading/simple_thread.cc
+++ b/base/threading/simple_thread.cc
@@ -1,22 +1,24 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright 2010 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/simple_thread.h"
-#include "base/logging.h"
+#include <ostream>
+
+#include "base/check.h"
#include "base/strings/string_number_conversions.h"
+#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
namespace base {
-SimpleThread::SimpleThread(const std::string& name_prefix)
- : SimpleThread(name_prefix, Options()) {}
+SimpleThread::SimpleThread(const std::string& name)
+ : SimpleThread(name, Options()) {}
-SimpleThread::SimpleThread(const std::string& name_prefix,
- const Options& options)
- : name_prefix_(name_prefix),
+SimpleThread::SimpleThread(const std::string& name, const Options& options)
+ : name_(name),
options_(options),
event_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED) {}
@@ -29,7 +31,7 @@
void SimpleThread::Start() {
StartAsync();
- ThreadRestrictions::ScopedAllowWait allow_wait;
+ ScopedAllowBaseSyncPrimitives allow_wait;
event_.Wait(); // Wait for the thread to complete initialization.
}
@@ -49,10 +51,10 @@
BeforeStart();
bool success =
options_.joinable
- ? PlatformThread::CreateWithPriority(options_.stack_size, this,
- &thread_, options_.priority)
- : PlatformThread::CreateNonJoinableWithPriority(
- options_.stack_size, this, options_.priority);
+ ? PlatformThread::CreateWithType(options_.stack_size, this, &thread_,
+ options_.thread_type)
+ : PlatformThread::CreateNonJoinableWithType(options_.stack_size, this,
+ options_.thread_type);
CHECK(success);
}
@@ -62,17 +64,12 @@
}
bool SimpleThread::HasBeenStarted() {
- ThreadRestrictions::ScopedAllowWait allow_wait;
return event_.IsSignaled();
}
void SimpleThread::ThreadMain() {
tid_ = PlatformThread::CurrentId();
- // Construct our full name of the form "name_prefix_/TID".
- std::string name(name_prefix_);
- name.push_back('/');
- name.append(IntToString(tid_));
- PlatformThread::SetName(name);
+ PlatformThread::SetName(name_);
// We've initialized our new thread, signal that we're done to Start().
event_.Signal();
@@ -82,14 +79,13 @@
}
DelegateSimpleThread::DelegateSimpleThread(Delegate* delegate,
- const std::string& name_prefix)
- : DelegateSimpleThread(delegate, name_prefix, Options()) {}
+ const std::string& name)
+ : DelegateSimpleThread(delegate, name, Options()) {}
DelegateSimpleThread::DelegateSimpleThread(Delegate* delegate,
- const std::string& name_prefix,
+ const std::string& name,
const Options& options)
- : SimpleThread(name_prefix, options),
- delegate_(delegate) {
+ : SimpleThread(name, options), delegate_(delegate) {
DCHECK(delegate_);
}
@@ -107,7 +103,7 @@
DelegateSimpleThreadPool::DelegateSimpleThreadPool(
const std::string& name_prefix,
- int num_threads)
+ size_t num_threads)
: name_prefix_(name_prefix),
num_threads_(num_threads),
dry_(WaitableEvent::ResetPolicy::MANUAL,
@@ -121,8 +117,11 @@
void DelegateSimpleThreadPool::Start() {
DCHECK(threads_.empty()) << "Start() called with outstanding threads.";
- for (int i = 0; i < num_threads_; ++i) {
- DelegateSimpleThread* thread = new DelegateSimpleThread(this, name_prefix_);
+ for (size_t i = 0; i < num_threads_; ++i) {
+ std::string name(name_prefix_);
+ name.push_back('/');
+ name.append(NumberToString(i));
+ DelegateSimpleThread* thread = new DelegateSimpleThread(this, name);
thread->Start();
threads_.push_back(thread);
}
@@ -135,7 +134,7 @@
AddWork(nullptr, num_threads_);
// Join and destroy all the worker threads.
- for (int i = 0; i < num_threads_; ++i) {
+ for (size_t i = 0; i < num_threads_; ++i) {
threads_[i]->Join();
delete threads_[i];
}
@@ -143,9 +142,10 @@
DCHECK(delegates_.empty());
}
-void DelegateSimpleThreadPool::AddWork(Delegate* delegate, int repeat_count) {
+void DelegateSimpleThreadPool::AddWork(Delegate* delegate,
+ size_t repeat_count) {
AutoLock locked(lock_);
- for (int i = 0; i < repeat_count; ++i)
+ for (size_t i = 0; i < repeat_count; ++i)
delegates_.push(delegate);
// If we were empty, signal that we have work now.
if (!dry_.IsSignaled())
diff --git a/base/threading/simple_thread.h b/base/threading/simple_thread.h
index c4ef5cc..ac7bc01 100644
--- a/base/threading/simple_thread.h
+++ b/base/threading/simple_thread.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -40,28 +40,31 @@
#ifndef BASE_THREADING_SIMPLE_THREAD_H_
#define BASE_THREADING_SIMPLE_THREAD_H_
+#include <stddef.h>
+
#include <string>
#include <vector>
#include "base/base_export.h"
#include "base/compiler_specific.h"
#include "base/containers/queue.h"
-#include "base/macros.h"
+#include "base/memory/raw_ptr.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
-#include "starboard/types.h"
namespace base {
// This is the base SimpleThread. You can derive from it and implement the
// virtual Run method, or you can use the DelegateSimpleThread interface.
+// SimpleThread should not be used to run a MessagePump, `base::Thread` must be
+// used for that.
class BASE_EXPORT SimpleThread : public PlatformThread::Delegate {
public:
struct BASE_EXPORT Options {
public:
Options() = default;
- explicit Options(ThreadPriority priority_in) : priority(priority_in) {}
+ explicit Options(ThreadType thread_type) : thread_type(thread_type) {}
~Options() = default;
// Allow copies.
@@ -71,7 +74,7 @@
// A custom stack size, or 0 for the system default.
size_t stack_size = 0;
- ThreadPriority priority = ThreadPriority::NORMAL;
+ ThreadType thread_type = ThreadType::kDefault;
// If false, the underlying thread's PlatformThreadHandle will not be kept
// around and as such the SimpleThread instance will not be Join()able and
@@ -80,12 +83,15 @@
bool joinable = true;
};
- // Create a SimpleThread. |options| should be used to manage any specific
+ // Creates a SimpleThread. |options| should be used to manage any specific
// configuration involving the thread creation and management.
- // Every thread has a name, in the form of |name_prefix|/TID, for example
- // "my_thread/321". The thread will not be created until Start() is called.
- explicit SimpleThread(const std::string& name_prefix);
- SimpleThread(const std::string& name_prefix, const Options& options);
+ // Every thread has a name, which is a display string to identify the thread.
+ // The thread will not be created until Start() is called.
+ explicit SimpleThread(const std::string& name);
+ SimpleThread(const std::string& name, const Options& options);
+
+ SimpleThread(const SimpleThread&) = delete;
+ SimpleThread& operator=(const SimpleThread&) = delete;
~SimpleThread() override;
@@ -118,7 +124,7 @@
bool HasBeenStarted();
// Returns True if Join() has ever been called.
- bool HasBeenJoined() { return joined_; }
+ bool HasBeenJoined() const { return joined_; }
// Returns true if Start() or StartAsync() has been called.
bool HasStartBeenAttempted() { return start_called_; }
@@ -139,8 +145,7 @@
// has been initialized before this is called.
virtual void BeforeJoin() {}
- const std::string name_prefix_;
- std::string name_;
+ const std::string name_;
const Options options_;
PlatformThreadHandle thread_; // PlatformThread handle, reset after Join.
WaitableEvent event_; // Signaled if Start() was ever called.
@@ -148,8 +153,6 @@
bool joined_ = false; // True if Join has been called.
// Set to true when the platform-thread creation has started.
bool start_called_ = false;
-
- DISALLOW_COPY_AND_ASSIGN(SimpleThread);
};
// A SimpleThread which delegates Run() to its Delegate. Non-joinable
@@ -171,13 +174,14 @@
const std::string& name_prefix,
const Options& options);
+ DelegateSimpleThread(const DelegateSimpleThread&) = delete;
+ DelegateSimpleThread& operator=(const DelegateSimpleThread&) = delete;
+
~DelegateSimpleThread() override;
void Run() override;
private:
- Delegate* delegate_;
-
- DISALLOW_COPY_AND_ASSIGN(DelegateSimpleThread);
+ raw_ptr<Delegate> delegate_;
};
// DelegateSimpleThreadPool allows you to start up a fixed number of threads,
@@ -194,7 +198,11 @@
public:
typedef DelegateSimpleThread::Delegate Delegate;
- DelegateSimpleThreadPool(const std::string& name_prefix, int num_threads);
+ DelegateSimpleThreadPool(const std::string& name_prefix, size_t num_threads);
+
+ DelegateSimpleThreadPool(const DelegateSimpleThreadPool&) = delete;
+ DelegateSimpleThreadPool& operator=(const DelegateSimpleThreadPool&) = delete;
+
~DelegateSimpleThreadPool() override;
// Start up all of the underlying threads, and start processing work if we
@@ -207,23 +215,18 @@
// It is safe to AddWork() any time, before or after Start().
// Delegate* should always be a valid pointer, NULL is reserved internally.
- void AddWork(Delegate* work, int repeat_count);
- void AddWork(Delegate* work) {
- AddWork(work, 1);
- }
+ void AddWork(Delegate* work, size_t repeat_count = 1);
// We implement the Delegate interface, for running our internal threads.
void Run() override;
private:
const std::string name_prefix_;
- int num_threads_;
+ size_t num_threads_;
std::vector<DelegateSimpleThread*> threads_;
base::queue<Delegate*> delegates_;
base::Lock lock_; // Locks delegates_
WaitableEvent dry_; // Not signaled when there is no work to do.
-
- DISALLOW_COPY_AND_ASSIGN(DelegateSimpleThreadPool);
};
} // namespace base
diff --git a/base/threading/simple_thread_unittest.cc b/base/threading/simple_thread_unittest.cc
index 4e618f9..ead6f8d 100644
--- a/base/threading/simple_thread_unittest.cc
+++ b/base/threading/simple_thread_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,6 +6,7 @@
#include "base/atomic_sequence_num.h"
#include "base/memory/ptr_util.h"
+#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/gtest_util.h"
@@ -19,15 +20,17 @@
class SetIntRunner : public DelegateSimpleThread::Delegate {
public:
SetIntRunner(int* ptr, int val) : ptr_(ptr), val_(val) { }
+
+ SetIntRunner(const SetIntRunner&) = delete;
+ SetIntRunner& operator=(const SetIntRunner&) = delete;
+
~SetIntRunner() override = default;
private:
void Run() override { *ptr_ = val_; }
- int* ptr_;
+ raw_ptr<int> ptr_;
int val_;
-
- DISALLOW_COPY_AND_ASSIGN(SetIntRunner);
};
// Signals |started_| when Run() is invoked and waits until |released_| is
@@ -43,6 +46,9 @@
done_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED) {}
+ ControlledRunner(const ControlledRunner&) = delete;
+ ControlledRunner& operator=(const ControlledRunner&) = delete;
+
~ControlledRunner() override { ReleaseAndWaitUntilDone(); }
void WaitUntilStarted() { started_.Wait(); }
@@ -62,13 +68,15 @@
WaitableEvent started_;
WaitableEvent released_;
WaitableEvent done_;
-
- DISALLOW_COPY_AND_ASSIGN(ControlledRunner);
};
class WaitEventRunner : public DelegateSimpleThread::Delegate {
public:
explicit WaitEventRunner(WaitableEvent* event) : event_(event) { }
+
+ WaitEventRunner(const WaitEventRunner&) = delete;
+ WaitEventRunner& operator=(const WaitEventRunner&) = delete;
+
~WaitEventRunner() override = default;
private:
@@ -78,21 +86,20 @@
EXPECT_TRUE(event_->IsSignaled());
}
- WaitableEvent* event_;
-
- DISALLOW_COPY_AND_ASSIGN(WaitEventRunner);
+ raw_ptr<WaitableEvent> event_;
};
class SeqRunner : public DelegateSimpleThread::Delegate {
public:
explicit SeqRunner(AtomicSequenceNumber* seq) : seq_(seq) { }
+ SeqRunner(const SeqRunner&) = delete;
+ SeqRunner& operator=(const SeqRunner&) = delete;
+
private:
void Run() override { seq_->GetNext(); }
- AtomicSequenceNumber* seq_;
-
- DISALLOW_COPY_AND_ASSIGN(SeqRunner);
+ raw_ptr<AtomicSequenceNumber> seq_;
};
// We count up on a sequence number, firing on the event when we've hit our
@@ -104,6 +111,9 @@
int total, WaitableEvent* event)
: seq_(seq), total_(total), event_(event) { }
+ VerifyPoolRunner(const VerifyPoolRunner&) = delete;
+ VerifyPoolRunner& operator=(const VerifyPoolRunner&) = delete;
+
private:
void Run() override {
if (seq_->GetNext() == total_) {
@@ -113,11 +123,9 @@
}
}
- AtomicSequenceNumber* seq_;
+ raw_ptr<AtomicSequenceNumber> seq_;
int total_;
- WaitableEvent* event_;
-
- DISALLOW_COPY_AND_ASSIGN(VerifyPoolRunner);
+ raw_ptr<WaitableEvent> event_;
};
} // namespace
diff --git a/base/threading/thread.cc b/base/threading/thread.cc
index bbaabea..716a2a4 100644
--- a/base/threading/thread.cc
+++ b/base/threading/thread.cc
@@ -1,39 +1,172 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread.h"
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/lazy_instance.h"
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include "base/dcheck_is_on.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/message_loop/message_pump.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
-#include "base/task/sequence_manager/sequence_manager.h"
+#include "base/task/current_thread.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/sequence_manager/task_queue.h"
+#include "base/task/single_thread_task_runner.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/threading/thread_id_name_manager.h"
-#include "base/threading/thread_local.h"
#include "base/threading/thread_restrictions.h"
+#include "base/types/pass_key.h"
#include "build/build_config.h"
+#include "third_party/abseil-cpp/absl/base/attributes.h"
-#if defined(OS_POSIX) && !defined(OS_NACL)
+#if defined(STARBOARD)
+#include <pthread.h>
+
+#include "base/check_op.h"
+#include "starboard/thread.h"
+#else
+#if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA)
#include "base/files/file_descriptor_watcher_posix.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
#endif
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#endif
+#endif
namespace base {
+#if DCHECK_IS_ON()
+namespace {
+
+#if defined(STARBOARD)
+ABSL_CONST_INIT pthread_once_t s_once_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_key = 0;
+
+void InitThreadLocalKey() {
+ int res = pthread_key_create(&s_thread_local_key , NULL);
+ DCHECK(res == 0);
+}
+
+void EnsureThreadLocalKeyInited() {
+ pthread_once(&s_once_flag, InitThreadLocalKey);
+}
+
+bool GetWasQuitProperly() {
+ EnsureThreadLocalKeyInited();
+ void* was_quit_properly = pthread_getspecific(s_thread_local_key);
+ return !!was_quit_properly ? reinterpret_cast<intptr_t>(was_quit_properly) != 0 : false;
+}
+#else
+// We use this thread-local variable to record whether or not a thread exited
+// because its Stop method was called. This allows us to catch cases where
+// MessageLoop::QuitWhenIdle() is called directly, which is unexpected when
+// using a Thread to setup and run a MessageLoop.
+ABSL_CONST_INIT thread_local bool was_quit_properly = false;
+#endif
+
+} // namespace
+#endif
+
+namespace internal {
+
+class SequenceManagerThreadDelegate : public Thread::Delegate {
+ public:
+ explicit SequenceManagerThreadDelegate(
+ MessagePumpType message_pump_type,
+ OnceCallback<std::unique_ptr<MessagePump>()> message_pump_factory)
+ : sequence_manager_(
+ sequence_manager::internal::CreateUnboundSequenceManagerImpl(
+ PassKey<base::internal::SequenceManagerThreadDelegate>(),
+ sequence_manager::SequenceManager::Settings::Builder()
+ .SetMessagePumpType(message_pump_type)
+ .Build())),
+ default_task_queue_(sequence_manager_->CreateTaskQueue(
+ sequence_manager::TaskQueue::Spec(
+ sequence_manager::QueueName::DEFAULT_TQ))),
+ message_pump_factory_(std::move(message_pump_factory)) {
+ sequence_manager_->SetDefaultTaskRunner(default_task_queue_->task_runner());
+ }
+
+ ~SequenceManagerThreadDelegate() override = default;
+
+ scoped_refptr<SingleThreadTaskRunner> GetDefaultTaskRunner() override {
+ // Surprisingly this might not be default_task_queue_->task_runner() which
+ // we set in the constructor. The Thread::Init() method could create a
+ // SequenceManager on top of the current one and call
+ // SequenceManager::SetDefaultTaskRunner which would propagate the new
+ // TaskRunner down to our SequenceManager. Turns out, code actually relies
+ // on this and somehow relies on
+ // SequenceManagerThreadDelegate::GetDefaultTaskRunner returning this new
+ // TaskRunner. So instead of returning default_task_queue_->task_runner() we
+ // need to query the SequenceManager for it.
+ // The underlying problem here is that Subclasses of Thread can do crazy
+ // stuff in Init() but they are not really in control of what happens in the
+ // Thread::Delegate, as this is passed in on calling StartWithOptions which
+ // could happen far away from where the Thread is created. We should
+ // consider getting rid of StartWithOptions, and pass them as a constructor
+ // argument instead.
+ return sequence_manager_->GetTaskRunner();
+ }
+
+ void BindToCurrentThread(TimerSlack timer_slack) override {
+ sequence_manager_->BindToMessagePump(
+ std::move(message_pump_factory_).Run());
+ sequence_manager_->SetTimerSlack(timer_slack);
+ }
+
+ private:
+ std::unique_ptr<sequence_manager::internal::SequenceManagerImpl>
+ sequence_manager_;
+ scoped_refptr<sequence_manager::TaskQueue> default_task_queue_;
+ OnceCallback<std::unique_ptr<MessagePump>()> message_pump_factory_;
+};
+
+} // namespace internal
+
Thread::Options::Options() = default;
-Thread::Options::Options(MessageLoop::Type type, size_t size)
- : message_loop_type(type), stack_size(size) {}
+Thread::Options::Options(MessagePumpType type, size_t size)
+ : message_pump_type(type), stack_size(size) {}
-Thread::Options::Options(const Options& other) = default;
+Thread::Options::Options(ThreadType thread_type) : thread_type(thread_type) {}
+
+Thread::Options::Options(Options&& other)
+ : message_pump_type(std::move(other.message_pump_type)),
+ delegate(std::move(other.delegate)),
+ timer_slack(std::move(other.timer_slack)),
+ message_pump_factory(std::move(other.message_pump_factory)),
+ stack_size(std::move(other.stack_size)),
+ thread_type(std::move(other.thread_type)),
+ joinable(std::move(other.joinable)) {
+ other.moved_from = true;
+}
+
+Thread::Options& Thread::Options::operator=(Thread::Options&& other) {
+ DCHECK_NE(this, &other);
+
+ message_pump_type = std::move(other.message_pump_type);
+ delegate = std::move(other.delegate);
+ timer_slack = std::move(other.timer_slack);
+ message_pump_factory = std::move(other.message_pump_factory);
+ stack_size = std::move(other.stack_size);
+ thread_type = std::move(other.thread_type);
+ joinable = std::move(other.joinable);
+ other.moved_from = true;
+
+ return *this;
+}
Thread::Options::~Options() = default;
@@ -41,9 +174,6 @@
: id_event_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED),
name_(name),
-#ifndef NDEBUG
- was_quit_properly_(false),
-#endif
start_event_(WaitableEvent::ResetPolicy::MANUAL,
WaitableEvent::InitialState::NOT_SIGNALED) {
// Only bind the sequence on Start(): the state is constant between
@@ -61,22 +191,23 @@
DCHECK(owning_sequence_checker_.CalledOnValidSequence());
Options options;
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
if (com_status_ == STA)
- options.message_loop_type = MessageLoop::TYPE_UI;
+ options.message_pump_type = MessagePumpType::UI;
#endif
- return StartWithOptions(options);
+ return StartWithOptions(std::move(options));
}
-bool Thread::StartWithOptions(const Options& options) {
+bool Thread::StartWithOptions(Options options) {
+ DCHECK(options.IsValid());
DCHECK(owning_sequence_checker_.CalledOnValidSequence());
- DCHECK(!message_loop_);
+ DCHECK(!delegate_);
DCHECK(!IsRunning());
DCHECK(!stopping_) << "Starting a non-joinable thread a second time? That's "
<< "not allowed!";
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
DCHECK((com_status_ != STA) ||
- (options.message_loop_type == MessageLoop::TYPE_UI));
+ (options.message_pump_type == MessagePumpType::UI));
#endif
// Reset |id_| here to support restarting the thread.
@@ -85,47 +216,43 @@
SetThreadWasQuitProperly(false);
- MessageLoop::Type type = options.message_loop_type;
- if (!options.message_pump_factory.is_null())
- type = MessageLoop::TYPE_CUSTOM;
+ timer_slack_ = options.timer_slack;
- message_loop_timer_slack_ = options.timer_slack;
- std::unique_ptr<MessageLoop> message_loop_owned =
- MessageLoop::CreateUnbound(type, options.message_pump_factory);
- message_loop_ = message_loop_owned.get();
- start_event_.Reset();
-
- if (options.on_sequence_manager_created) {
- sequence_manager_ =
- sequence_manager::CreateUnboundSequenceManager(message_loop_);
- options.on_sequence_manager_created.Run(sequence_manager_.get());
+ if (options.delegate) {
+ DCHECK(!options.message_pump_factory);
+ delegate_ = std::move(options.delegate);
+ } else if (options.message_pump_factory) {
+ delegate_ = std::make_unique<internal::SequenceManagerThreadDelegate>(
+ MessagePumpType::CUSTOM, options.message_pump_factory);
+ } else {
+ delegate_ = std::make_unique<internal::SequenceManagerThreadDelegate>(
+ options.message_pump_type,
+ BindOnce([](MessagePumpType type) { return MessagePump::Create(type); },
+ options.message_pump_type));
}
+ start_event_.Reset();
+
// Hold |thread_lock_| while starting the new thread to synchronize with
// Stop() while it's not guaranteed to be sequenced (until crbug/629139 is
// fixed).
{
AutoLock lock(thread_lock_);
- bool success =
- options.joinable
- ? PlatformThread::CreateWithPriority(options.stack_size, this,
- &thread_, options.priority)
- : PlatformThread::CreateNonJoinableWithPriority(
- options.stack_size, this, options.priority);
+ bool success = options.joinable
+ ? PlatformThread::CreateWithType(
+ options.stack_size, this, &thread_,
+ options.thread_type, options.message_pump_type)
+ : PlatformThread::CreateNonJoinableWithType(
+ options.stack_size, this, options.thread_type,
+ options.message_pump_type);
if (!success) {
DLOG(ERROR) << "failed to create thread";
- message_loop_ = nullptr;
return false;
}
}
joinable_ = options.joinable;
- // The ownership of |message_loop_| is managed by the newly created thread
- // within the ThreadMain.
- ignore_result(message_loop_owned.release());
-
- DCHECK(message_loop_);
return true;
}
@@ -140,16 +267,17 @@
bool Thread::WaitUntilThreadStarted() const {
DCHECK(owning_sequence_checker_.CalledOnValidSequence());
- if (!message_loop_)
+ if (!delegate_)
return false;
- base::ThreadRestrictions::ScopedAllowWait allow_wait;
+ // https://crbug.com/918039
+ base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
start_event_.Wait();
return true;
}
void Thread::FlushForTesting() {
DCHECK(owning_sequence_checker_.CalledOnValidSequence());
- if (!message_loop_)
+ if (!delegate_)
return;
WaitableEvent done(WaitableEvent::ResetPolicy::AUTOMATIC,
@@ -176,15 +304,14 @@
// Wait for the thread to exit.
//
- // TODO(darin): Unfortunately, we need to keep |message_loop_| around until
- // the thread exits. Some consumers are abusing the API. Make them stop.
- //
+ // TODO(darin): Unfortunately, we need to keep |delegate_| around
+ // until the thread exits. Some consumers are abusing the API. Make them stop.
PlatformThread::Join(thread_);
thread_ = base::PlatformThreadHandle();
- // The thread should nullify |message_loop_| on exit (note: Join() adds an
- // implicit memory barrier and no lock is thus required for this check).
- DCHECK(!message_loop_);
+ // The thread should release |delegate_| on exit (note: Join() adds
+ // an implicit memory barrier and no lock is thus required for this check).
+ DCHECK(!delegate_);
stopping_ = false;
}
@@ -194,20 +321,11 @@
// enable this check.
// DCHECK(owning_sequence_checker_.CalledOnValidSequence());
- if (stopping_ || !message_loop_)
+ if (stopping_ || !delegate_)
return;
stopping_ = true;
- if (using_external_message_loop_) {
- // Setting |stopping_| to true above should have been sufficient for this
- // thread to be considered "stopped" per it having never set its |running_|
- // bit by lack of its own ThreadMain.
- DCHECK(!IsRunning());
- message_loop_ = nullptr;
- return;
- }
-
task_runner()->PostTask(
FROM_HERE, base::BindOnce(&Thread::ThreadQuitHelper, Unretained(this)));
}
@@ -218,27 +336,24 @@
}
PlatformThreadId Thread::GetThreadId() const {
- // If the thread is created but not started yet, wait for |id_| being ready.
- base::ThreadRestrictions::ScopedAllowWait allow_wait;
- id_event_.Wait();
+ if (!id_event_.IsSignaled()) {
+ // If the thread is created but not started yet, wait for |id_| being ready.
+ base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait;
+ id_event_.Wait();
+ }
return id_;
}
-PlatformThreadHandle Thread::GetThreadHandle() const {
- AutoLock lock(thread_lock_);
- return thread_;
-}
-
bool Thread::IsRunning() const {
// TODO(gab): Fix improper usage of this API (http://crbug.com/629139) and
// enable this check.
// DCHECK(owning_sequence_checker_.CalledOnValidSequence());
- // If the thread's already started (i.e. |message_loop_| is non-null) and not
- // yet requested to stop (i.e. |stopping_| is false) we can just return true.
- // (Note that |stopping_| is touched only on the same sequence that starts /
- // started the new thread so we need no locking here.)
- if (message_loop_ && !stopping_)
+ // If the thread's already started (i.e. |delegate_| is non-null) and
+ // not yet requested to stop (i.e. |stopping_| is false) we can just return
+ // true. (Note that |stopping_| is touched only on the same sequence that
+ // starts / started the new thread so we need no locking here.)
+ if (delegate_ && !stopping_)
return true;
// Otherwise check the |running_| flag, which is set to true by the new thread
// only while it is inside Run().
@@ -256,33 +371,29 @@
// static
void Thread::SetThreadWasQuitProperly(bool flag) {
-#ifndef NDEBUG
- was_quit_properly_ = flag;
+#if DCHECK_IS_ON()
+#if defined(STARBOARD)
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, reinterpret_cast<void*>(static_cast<intptr_t>(flag)));
+#else
+ was_quit_properly = flag;
+#endif
#endif
}
// static
bool Thread::GetThreadWasQuitProperly() {
-#ifndef NDEBUG
- return was_quit_properly_;
+#if DCHECK_IS_ON()
+#if defined(STARBOARD)
+ return GetWasQuitProperly();
+#else
+ return was_quit_properly;
+#endif
#else
return true;
#endif
}
-void Thread::SetMessageLoop(MessageLoop* message_loop) {
- DCHECK(owning_sequence_checker_.CalledOnValidSequence());
- DCHECK(message_loop);
-
- // Setting |message_loop_| should suffice for this thread to be considered
- // as "running", until Stop() is invoked.
- DCHECK(!IsRunning());
- message_loop_ = message_loop;
- DCHECK(IsRunning());
-
- using_external_message_loop_ = true;
-}
-
void Thread::ThreadMain() {
// First, make GetThreadId() available to avoid deadlocks. It could be called
// any place in the following thread initialization code.
@@ -299,42 +410,32 @@
PlatformThread::SetName(name_.c_str());
ANNOTATE_THREAD_NAME(name_.c_str()); // Tell the name to race detector.
- if (sequence_manager_) {
- // Bind the SequenceManager before binding the MessageLoop, so that the
- // TaskQueues are bound before the MessageLoop. This is required as one of
- // the TaskQueues may have already replaced the MessageLoop's TaskRunner,
- // and the MessageLoop's TaskRunner needs to be associated with this thread
- // when we call MessageLoop::BindToCurrentThread().
- sequence_manager_->BindToCurrentThread();
- }
-
// Lazily initialize the |message_loop| so that it can run on this thread.
- DCHECK(message_loop_);
- std::unique_ptr<MessageLoop> message_loop(message_loop_);
- message_loop_->BindToCurrentThread();
- message_loop_->SetTimerSlack(message_loop_timer_slack_);
-
- if (sequence_manager_) {
- sequence_manager_->CompleteInitializationOnBoundThread();
- }
-
-#if defined(OS_POSIX) && !defined(OS_NACL)
+ DCHECK(delegate_);
+ // This binds CurrentThread and SingleThreadTaskRunner::CurrentDefaultHandle.
+ delegate_->BindToCurrentThread(timer_slack_);
+ DCHECK(CurrentThread::Get());
+ DCHECK(SingleThreadTaskRunner::HasCurrentDefault());
+#if !defined(STARBOARD)
+#if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA)
// Allow threads running a MessageLoopForIO to use FileDescriptorWatcher API.
std::unique_ptr<FileDescriptorWatcher> file_descriptor_watcher;
- if (MessageLoopForIO::IsCurrent()) {
- file_descriptor_watcher.reset(new FileDescriptorWatcher(
- static_cast<MessageLoopForIO*>(message_loop_)));
+ if (CurrentIOThread::IsSet()) {
+ file_descriptor_watcher = std::make_unique<FileDescriptorWatcher>(
+ delegate_->GetDefaultTaskRunner());
}
-#endif
+#endif // (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_NACL)) || BUILDFLAG(IS_FUCHSIA)
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
std::unique_ptr<win::ScopedCOMInitializer> com_initializer;
if (com_status_ != NONE) {
- com_initializer.reset((com_status_ == STA) ?
- new win::ScopedCOMInitializer() :
- new win::ScopedCOMInitializer(win::ScopedCOMInitializer::kMTA));
+ com_initializer.reset(
+ (com_status_ == STA)
+ ? new win::ScopedCOMInitializer()
+ : new win::ScopedCOMInitializer(win::ScopedCOMInitializer::kMTA));
}
#endif
+#endif
// Let the thread do extra initialization.
Init();
@@ -358,22 +459,15 @@
// Let the thread do extra cleanup.
CleanUp();
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
com_initializer.reset();
#endif
- sequence_manager_.reset();
-
- if (message_loop->type() != MessageLoop::TYPE_CUSTOM) {
- // Assert that RunLoop::QuitWhenIdle was called by ThreadQuitHelper. Don't
- // check for custom message pumps, because their shutdown might not allow
- // this.
- DCHECK(GetThreadWasQuitProperly());
- }
+ DCHECK(GetThreadWasQuitProperly());
// We can't receive messages anymore.
// (The message loop is destructed at the end of this block)
- message_loop_ = nullptr;
+ delegate_.reset();
run_loop_ = nullptr;
}
diff --git a/base/threading/thread.h b/base/threading/thread.h
index a27dc2d..06dd702 100644
--- a/base/threading/thread.h
+++ b/base/threading/thread.h
@@ -1,42 +1,36 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_THREAD_H_
#define BASE_THREADING_THREAD_H_
+#include <stddef.h>
+
#include <memory>
#include <string>
#include "base/base_export.h"
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/message_loop/message_loop.h"
+#include "base/check.h"
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr_exclusion.h"
+#include "base/message_loop/message_pump_type.h"
#include "base/message_loop/timer_slack.h"
#include "base/sequence_checker.h"
-#include "base/single_thread_task_runner.h"
#include "base/synchronization/atomic_flag.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
+#include "base/task/single_thread_task_runner.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
-#include "starboard/types.h"
namespace base {
class MessagePump;
class RunLoop;
-#if defined(STARBOARD)
-const size_t kUnitTestStackSize = 1024 * 1024;
-#endif
-
-namespace sequence_manager {
-class SequenceManager;
-} // namespace sequence_manager
-
// IMPORTANT: Instead of creating a base::Thread, consider using
-// base::Create(Sequenced|SingleThread)TaskRunnerWithTraits().
+// base::ThreadPool::Create(Sequenced|SingleThread)TaskRunner().
//
// A simple thread abstraction that establishes a MessageLoop on a new thread.
// The consumer uses the MessageLoop of the thread to cause code to execute on
@@ -50,7 +44,7 @@
//
// (1) Thread::CleanUp()
// (2) MessageLoop::~MessageLoop
-// (3.b) MessageLoopCurrent::DestructionObserver::WillDestroyCurrentMessageLoop
+// (3.b) CurrentThread::DestructionObserver::WillDestroyCurrentMessageLoop
//
// This API is not thread-safe: unless indicated otherwise its methods are only
// valid from the owning sequence (which is the one from which Start() is
@@ -65,42 +59,54 @@
// Thread object (including ~Thread()).
class BASE_EXPORT Thread : PlatformThread::Delegate {
public:
+ class BASE_EXPORT Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ virtual scoped_refptr<SingleThreadTaskRunner> GetDefaultTaskRunner() = 0;
+
+ // Binds a RunLoop::Delegate and task runner CurrentDefaultHandle to the
+ // thread. The underlying MessagePump will have its |timer_slack| set to the
+ // specified amount.
+ virtual void BindToCurrentThread(TimerSlack timer_slack) = 0;
+ };
+
struct BASE_EXPORT Options {
- typedef Callback<std::unique_ptr<MessagePump>()> MessagePumpFactory;
- using SequenceManagerCreatedCallback =
- RepeatingCallback<void(sequence_manager::SequenceManager*)>;
+ using MessagePumpFactory =
+ RepeatingCallback<std::unique_ptr<MessagePump>()>;
Options();
- Options(MessageLoop::Type type, size_t size);
- Options(const Options& other);
+ Options(MessagePumpType type, size_t size);
+ explicit Options(ThreadType thread_type);
+ Options(Options&& other);
+ Options& operator=(Options&& other);
~Options();
- // Specifies the type of message loop that will be allocated on the thread.
+ // Specifies the type of message pump that will be allocated on the thread.
// This is ignored if message_pump_factory.is_null() is false.
- MessageLoop::Type message_loop_type = MessageLoop::TYPE_DEFAULT;
+ MessagePumpType message_pump_type = MessagePumpType::DEFAULT;
+
+ // An unbound Delegate that will be bound to the thread. Ownership
+ // of |delegate| will be transferred to the thread.
+ std::unique_ptr<Delegate> delegate = nullptr;
// Specifies timer slack for thread message loop.
TimerSlack timer_slack = TIMER_SLACK_NONE;
// Used to create the MessagePump for the MessageLoop. The callback is Run()
// on the thread. If message_pump_factory.is_null(), then a MessagePump
- // appropriate for |message_loop_type| is created. Setting this forces the
- // MessageLoop::Type to TYPE_CUSTOM.
+ // appropriate for |message_pump_type| is created. Setting this forces the
+ // MessagePumpType to TYPE_CUSTOM. This is not compatible with a non-null
+ // |delegate|.
MessagePumpFactory message_pump_factory;
- // If set, the Thread will create a SequenceManager on the MessageLoop and
- // execute the provided callback right after it was created. The callback
- // will be executed on the creator thread before the new Thread is started.
- // It is typically used to create TaskQueues for the SequenceManager.
- SequenceManagerCreatedCallback on_sequence_manager_created;
-
// Specifies the maximum stack size that the thread is allowed to use.
// This does not necessarily correspond to the thread's initial stack size.
// A value of 0 indicates that the default maximum should be used.
size_t stack_size = 0;
- // Specifies the initial thread priority.
- ThreadPriority priority = ThreadPriority::NORMAL;
+ // Specifies the initial thread type.
+ ThreadType thread_type = ThreadType::kDefault;
// If false, the thread will not be joined on destruction. This is intended
// for threads that want TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN
@@ -109,20 +115,21 @@
// TODO(gab): allow non-joinable instances to be deleted without causing
// user-after-frees (proposal @ https://crbug.com/629139#c14)
bool joinable = true;
+
+ bool IsValid() const { return !moved_from; }
+
+ private:
+ // Set to true when the object is moved into another. Use to prevent reuse
+ // of a moved-from object.
+ bool moved_from = false;
};
// Constructor.
// name is a display string to identify the thread.
explicit Thread(const std::string& name);
-#if !defined(BASE_DONT_ENFORCE_THREAD_NAME_LENGTH)
- // Constructor, checks length of name literal at compile-time
- template <size_t N>
- explicit Thread(char const (&name)[N]) : Thread(std::string(name)) {
- // 16 is common to Linux and other Starboard platforms
- static_assert(N <= 16, "Thread name too long, max 16");
- }
-#endif
+ Thread(const Thread&) = delete;
+ Thread& operator=(const Thread&) = delete;
// Destroys the thread, stopping it if necessary.
//
@@ -134,14 +141,14 @@
// before it is destructed.
~Thread() override;
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
// Causes the thread to initialize COM. This must be called before calling
// Start() or StartWithOptions(). If |use_mta| is false, the thread is also
// started with a TYPE_UI message loop. It is an error to call
// init_com_with_mta(false) and then StartWithOptions() with any message loop
// type other than TYPE_UI.
void init_com_with_mta(bool use_mta) {
- DCHECK(!message_loop_);
+ DCHECK(!delegate_);
com_status_ = use_mta ? MTA : STA;
}
#endif
@@ -161,7 +168,7 @@
// Note: This function can't be called on Windows with the loader lock held;
// i.e. during a DllMain, global object construction or destruction, atexit()
// callback.
- bool StartWithOptions(const Options& options);
+ bool StartWithOptions(Options options);
// Starts the thread and wait for the thread to start and run initialization
// before returning. It's same as calling Start() and then
@@ -213,34 +220,6 @@
// relationship with this one.
void DetachFromSequence();
- // Returns the message loop for this thread. Use the MessageLoop's
- // PostTask methods to execute code on the thread. This only returns
- // non-null after a successful call to Start. After Stop has been called,
- // this will return nullptr.
- //
- // NOTE: You must not call this MessageLoop's Quit method directly. Use
- // the Thread's Stop method instead.
- //
- // In addition to this Thread's owning sequence, this can also safely be
- // called from the underlying thread itself.
- MessageLoop* message_loop() const {
- // This class doesn't provide synchronization around |message_loop_| and as
- // such only the owner should access it (and the underlying thread which
- // never sees it before it's set). In practice, many callers are coming from
- // unrelated threads but provide their own implicit (e.g. memory barriers
- // from task posting) or explicit (e.g. locks) synchronization making the
- // access of |message_loop_| safe... Changing all of those callers is
- // unfeasible; instead verify that they can reliably see
- // |message_loop_ != nullptr| without synchronization as a proof that their
- // external synchronization catches the unsynchronized effects of Start().
- // TODO(gab): Despite all of the above this test has to be disabled for now
- // per crbug.com/629139#c6.
- // DCHECK(owning_sequence_checker_.CalledOnValidSequence() ||
- // (id_event_.IsSignaled() && id_ == PlatformThread::CurrentId()) ||
- // message_loop_);
- return message_loop_;
- }
-
// Returns a TaskRunner for this thread. Use the TaskRunner's PostTask
// methods to execute code on the thread. Returns nullptr if the thread is not
// running (e.g. before Start or after Stop have been called). Callers can
@@ -250,11 +229,20 @@
// In addition to this Thread's owning sequence, this can also safely be
// called from the underlying thread itself.
scoped_refptr<SingleThreadTaskRunner> task_runner() const {
- // Refer to the DCHECK and comment inside |message_loop()|.
+ // This class doesn't provide synchronization around |message_loop_base_|
+ // and as such only the owner should access it (and the underlying thread
+ // which never sees it before it's set). In practice, many callers are
+ // coming from unrelated threads but provide their own implicit (e.g. memory
+ // barriers from task posting) or explicit (e.g. locks) synchronization
+ // making the access of |message_loop_base_| safe... Changing all of those
+ // callers is unfeasible; instead verify that they can reliably see
+ // |message_loop_base_ != nullptr| without synchronization as a proof that
+ // their external synchronization catches the unsynchronized effects of
+ // Start().
DCHECK(owning_sequence_checker_.CalledOnValidSequence() ||
(id_event_.IsSignaled() && id_ == PlatformThread::CurrentId()) ||
- message_loop_);
- return message_loop_ ? message_loop_->task_runner() : nullptr;
+ delegate_);
+ return delegate_ ? delegate_->GetDefaultTaskRunner() : nullptr;
}
// Returns the name of this thread (for display in debugger too).
@@ -269,15 +257,6 @@
// This method is thread-safe.
PlatformThreadId GetThreadId() const;
- // Returns the current thread handle. If called before Start*() returns or
- // after Stop() returns, an empty thread handle will be returned.
- //
- // This method is thread-safe.
- //
- // TODO(robliao): Remove this when it no longer needs to be temporarily
- // exposed for http://crbug.com/717380.
- PlatformThreadHandle GetThreadHandle() const;
-
// Returns true if the thread has been started, and not yet stopped.
bool IsRunning() const;
@@ -291,21 +270,15 @@
// Called just after the message loop ends
virtual void CleanUp() {}
- void SetThreadWasQuitProperly(bool flag);
- bool GetThreadWasQuitProperly();
-
- // Bind this Thread to an existing MessageLoop instead of starting a new one.
- // TODO(gab): Remove this after ios/ has undergone the same surgery as
- // BrowserThreadImpl (ref.
- // https://chromium-review.googlesource.com/c/chromium/src/+/969104).
- void SetMessageLoop(MessageLoop* message_loop);
-
- bool using_external_message_loop() const {
- return using_external_message_loop_;
- }
+ static void SetThreadWasQuitProperly(bool flag);
+ static bool GetThreadWasQuitProperly();
private:
-#if defined(OS_WIN)
+ // Friends for message_loop() access:
+ friend class MessageLoopTaskRunnerTest;
+ friend class ScheduleWorkTest;
+
+#if BUILDFLAG(IS_WIN)
enum ComStatus {
NONE,
STA,
@@ -318,7 +291,7 @@
void ThreadQuitHelper();
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
// Whether this thread needs to initialize COM, and if so, in what mode.
ComStatus com_status_ = NONE;
#endif
@@ -346,44 +319,26 @@
// Protects |id_| which must only be read while it's signaled.
mutable WaitableEvent id_event_;
- // The thread's MessageLoop and RunLoop. Valid only while the thread is alive.
- // Set by the created thread.
- MessageLoop* message_loop_ = nullptr;
- RunLoop* run_loop_ = nullptr;
+ // The thread's Delegate and RunLoop are valid only while the thread is
+ // alive. Set by the created thread.
+ std::unique_ptr<Delegate> delegate_;
+ // This field is not a raw_ptr<> because it was filtered by the rewriter for:
+ // #union
+ RAW_PTR_EXCLUSION RunLoop* run_loop_ = nullptr;
- // True only if |message_loop_| was externally provided by |SetMessageLoop()|
- // in which case this Thread has no underlying |thread_| and should merely
- // drop |message_loop_| on Stop(). In that event, this remains true after
- // Stop() was invoked so that subclasses can use this state to build their own
- // cleanup logic as required.
- bool using_external_message_loop_ = false;
-
- // Optionally stores a SequenceManager that manages Tasks on the MessageLoop.
- std::unique_ptr<sequence_manager::SequenceManager> sequence_manager_;
-
- // Stores Options::timer_slack_ until the message loop has been bound to
+ // Stores Options::timer_slack_ until the sequence manager has been bound to
// a thread.
- TimerSlack message_loop_timer_slack_ = TIMER_SLACK_NONE;
+ TimerSlack timer_slack_ = TIMER_SLACK_NONE;
// The name of the thread. Used for debugging purposes.
const std::string name_;
-#ifndef NDEBUG
- // We use this member variable to record whether or not a thread exited
- // because its Stop method was called. This allows us to catch cases where
- // MessageLoop::QuitWhenIdle() is called directly, which is unexpected when
- // using a Thread to setup and run a MessageLoop.
- bool was_quit_properly_;
-#endif
-
// Signaled when the created thread gets ready to use the message loop.
mutable WaitableEvent start_event_;
// This class is not thread-safe, use this to verify access from the owning
// sequence of the Thread.
SequenceChecker owning_sequence_checker_;
-
- DISALLOW_COPY_AND_ASSIGN(Thread);
};
} // namespace base
diff --git a/base/threading/thread_checker.cc b/base/threading/thread_checker.cc
new file mode 100644
index 0000000..4baec82
--- /dev/null
+++ b/base/threading/thread_checker.cc
@@ -0,0 +1,39 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/thread_checker.h"
+
+#if DCHECK_IS_ON()
+#include <memory>
+#include <ostream>
+
+#include "base/check.h"
+#include "base/debug/stack_trace.h"
+#endif
+
+namespace base {
+
+#if DCHECK_IS_ON()
+ScopedValidateThreadChecker::ScopedValidateThreadChecker(
+ const ThreadChecker& checker) {
+ std::unique_ptr<debug::StackTrace> bound_at;
+ DCHECK(checker.CalledOnValidThread(&bound_at))
+ << (bound_at ? "\nWas attached to thread at:\n" + bound_at->ToString()
+ : "");
+}
+
+ScopedValidateThreadChecker::ScopedValidateThreadChecker(
+ const ThreadChecker& checker,
+ const StringPiece& msg) {
+ std::unique_ptr<debug::StackTrace> bound_at;
+ DCHECK(checker.CalledOnValidThread(&bound_at))
+ << msg
+ << (bound_at ? "\nWas attached to thread at:\n" + bound_at->ToString()
+ : "");
+}
+
+ScopedValidateThreadChecker::~ScopedValidateThreadChecker() = default;
+#endif // DCHECK_IS_ON()
+
+} // namespace base
diff --git a/base/threading/thread_checker.h b/base/threading/thread_checker.h
index 6799e25..3c1ff49 100644
--- a/base/threading/thread_checker.h
+++ b/base/threading/thread_checker.h
@@ -1,16 +1,19 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_THREAD_CHECKER_H_
#define BASE_THREADING_THREAD_CHECKER_H_
-#include "base/compiler_specific.h"
-#include "base/logging.h"
+#include "base/base_export.h"
+#include "base/dcheck_is_on.h"
+#include "base/strings/string_piece.h"
+#include "base/thread_annotations.h"
#include "base/threading/thread_checker_impl.h"
// ThreadChecker is a helper class used to help verify that some methods of a
-// class are called from the same thread (for thread-affinity).
+// class are called from the same thread (for thread-affinity). It supports
+// thread safety annotations (see base/thread_annotations.h).
//
// Use the macros below instead of the ThreadChecker directly so that the unused
// member doesn't result in an extra byte (four when padded) per instance in
@@ -31,6 +34,12 @@
// SequencedTaskRunner for ease of scheduling as well as minimizes side-effects
// if that change is made.
//
+// Debugging:
+// If ThreadChecker::EnableStackLogging() is called beforehand, then when
+// ThreadChecker fails, in addition to crashing with a stack trace of where
+// the violation occurred, it will also dump a stack trace of where the
+// checker was bound to a thread.
+//
// Usage:
// class MyClass {
// public:
@@ -38,7 +47,7 @@
// // It's sometimes useful to detach on construction for objects that are
// // constructed in one place and forever after used from another
// // thread.
-// DETACH_FROM_THREAD(my_thread_checker_);
+// DETACH_FROM_THREAD(thread_checker_);
// }
//
// ~MyClass() {
@@ -48,25 +57,40 @@
// // knows usage on the associated thread is done. If you're not
// // detaching in the constructor, you probably want to explicitly check
// // in the destructor.
-// DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
+// DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// }
//
// void MyMethod() {
-// DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
+// DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// ... (do stuff) ...
// }
//
+// void MyOtherMethod()
+// VALID_CONTEXT_REQUIRED(thread_checker_) {
+// foo_ = 42;
+// }
+//
// private:
-// THREAD_CHECKER(my_thread_checker_);
+// int foo_ GUARDED_BY_CONTEXT(thread_checker_);
+//
+// THREAD_CHECKER(thread_checker_);
// }
+#define THREAD_CHECKER_INTERNAL_CONCAT2(a, b) a##b
+#define THREAD_CHECKER_INTERNAL_CONCAT(a, b) \
+ THREAD_CHECKER_INTERNAL_CONCAT2(a, b)
+#define THREAD_CHECKER_INTERNAL_UID(prefix) \
+ THREAD_CHECKER_INTERNAL_CONCAT(prefix, __LINE__)
+
#if DCHECK_IS_ON()
#define THREAD_CHECKER(name) base::ThreadChecker name
-#define DCHECK_CALLED_ON_VALID_THREAD(name) DCHECK((name).CalledOnValidThread())
+#define DCHECK_CALLED_ON_VALID_THREAD(name, ...) \
+ base::ScopedValidateThreadChecker THREAD_CHECKER_INTERNAL_UID( \
+ scoped_validate_thread_checker_)(name, ##__VA_ARGS__);
#define DETACH_FROM_THREAD(name) (name).DetachFromThread()
#else // DCHECK_IS_ON()
-#define THREAD_CHECKER(name)
-#define DCHECK_CALLED_ON_VALID_THREAD(name) EAT_STREAM_PARAMETERS
+#define THREAD_CHECKER(name) static_assert(true, "")
+#define DCHECK_CALLED_ON_VALID_THREAD(name, ...) EAT_CHECK_STREAM_PARAMS()
#define DETACH_FROM_THREAD(name)
#endif // DCHECK_IS_ON()
@@ -76,20 +100,33 @@
//
// Note: You should almost always use the ThreadChecker class (through the above
// macros) to get the right version for your build configuration.
-class ThreadCheckerDoNothing {
+// Note: This is only a check, not a "lock". It is marked "LOCKABLE" only in
+// order to support thread_annotations.h.
+class LOCKABLE ThreadCheckerDoNothing {
public:
- ThreadCheckerDoNothing() = default;
- bool CalledOnValidThread() const WARN_UNUSED_RESULT { return true; }
- void DetachFromThread() {}
+ static void EnableStackLogging() {}
- private:
- DISALLOW_COPY_AND_ASSIGN(ThreadCheckerDoNothing);
+ ThreadCheckerDoNothing() = default;
+
+ ThreadCheckerDoNothing(const ThreadCheckerDoNothing&) = delete;
+ ThreadCheckerDoNothing& operator=(const ThreadCheckerDoNothing&) = delete;
+
+ // Moving between matching threads is allowed to help classes with
+ // ThreadCheckers that want a default move-construct/assign.
+ ThreadCheckerDoNothing(ThreadCheckerDoNothing&& other) = default;
+ ThreadCheckerDoNothing& operator=(ThreadCheckerDoNothing&& other) = default;
+
+ [[nodiscard]] bool CalledOnValidThread(
+ std::unique_ptr<void*> = nullptr) const {
+ return true;
+ }
+ void DetachFromThread() {}
};
// Note that ThreadCheckerImpl::CalledOnValidThread() returns false when called
// from tasks posted to SingleThreadTaskRunners bound to different sequences,
// even if the tasks happen to run on the same thread (e.g. two independent
-// SingleThreadTaskRunners on the TaskScheduler that happen to share a thread).
+// SingleThreadTaskRunners on the ThreadPool that happen to share a thread).
#if DCHECK_IS_ON()
class ThreadChecker : public ThreadCheckerImpl {
};
@@ -98,6 +135,23 @@
};
#endif // DCHECK_IS_ON()
+#if DCHECK_IS_ON()
+class BASE_EXPORT SCOPED_LOCKABLE ScopedValidateThreadChecker {
+ public:
+ explicit ScopedValidateThreadChecker(const ThreadChecker& checker)
+ EXCLUSIVE_LOCK_FUNCTION(checker);
+ ScopedValidateThreadChecker(const ThreadChecker& checker,
+ const StringPiece& msg)
+ EXCLUSIVE_LOCK_FUNCTION(checker);
+
+ ScopedValidateThreadChecker(const ScopedValidateThreadChecker&) = delete;
+ ScopedValidateThreadChecker& operator=(const ScopedValidateThreadChecker&) =
+ delete;
+
+ ~ScopedValidateThreadChecker() UNLOCK_FUNCTION();
+};
+#endif
+
} // namespace base
#endif // BASE_THREADING_THREAD_CHECKER_H_
diff --git a/base/threading/thread_checker_impl.cc b/base/threading/thread_checker_impl.cc
index e3bc8be..1d59088 100644
--- a/base/threading/thread_checker_impl.cc
+++ b/base/threading/thread_checker_impl.cc
@@ -1,69 +1,146 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread_checker_impl.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/check.h"
+#include "base/debug/stack_trace.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_local.h"
+
+namespace {
+bool g_log_thread_and_sequence_checker_binding = false;
+}
namespace base {
+
+// static
+void ThreadCheckerImpl::EnableStackLogging() {
+ g_log_thread_and_sequence_checker_binding = true;
+}
+
ThreadCheckerImpl::ThreadCheckerImpl() {
-#if defined(STARBOARD)
- starboard::ScopedSpinLock lock(&members_lock_);
-#else // defined(STARBOARD)
AutoLock auto_lock(lock_);
-#endif // defined(STARBOARD)
EnsureAssigned();
}
ThreadCheckerImpl::~ThreadCheckerImpl() = default;
-bool ThreadCheckerImpl::CalledOnValidThread() const {
-#if defined(STARBOARD)
- starboard::ScopedSpinLock lock(&members_lock_);
-#else // defined(STARBOARD)
+ThreadCheckerImpl::ThreadCheckerImpl(ThreadCheckerImpl&& other) {
+ // Verify that |other| is called on its associated thread and bind it now if
+ // it is currently detached (even if this isn't a DCHECK build).
+ const bool other_called_on_valid_thread = other.CalledOnValidThread();
+ DCHECK(other_called_on_valid_thread);
+
+ // Intentionally not using |other.lock_| to let TSAN catch racy construct from
+ // |other|.
+ bound_at_ = std::move(other.bound_at_);
+ thread_id_ = other.thread_id_;
+ task_token_ = other.task_token_;
+ sequence_token_ = other.sequence_token_;
+
+ // other.bound_at_ was moved from so it's null.
+ other.thread_id_ = PlatformThreadRef();
+ other.task_token_ = TaskToken();
+ other.sequence_token_ = SequenceToken();
+}
+
+ThreadCheckerImpl& ThreadCheckerImpl::operator=(ThreadCheckerImpl&& other) {
+ DCHECK(CalledOnValidThread());
+
+ // Verify that |other| is called on its associated thread and bind it now if
+ // it is currently detached (even if this isn't a DCHECK build).
+ const bool other_called_on_valid_thread = other.CalledOnValidThread();
+ DCHECK(other_called_on_valid_thread);
+
+ // Intentionally not using either |lock_| to let TSAN catch racy assign.
+ TS_UNCHECKED_READ(thread_id_) = TS_UNCHECKED_READ(other.thread_id_);
+ TS_UNCHECKED_READ(task_token_) = TS_UNCHECKED_READ(other.task_token_);
+ TS_UNCHECKED_READ(sequence_token_) = TS_UNCHECKED_READ(other.sequence_token_);
+
+ TS_UNCHECKED_READ(other.thread_id_) = PlatformThreadRef();
+ TS_UNCHECKED_READ(other.task_token_) = TaskToken();
+ TS_UNCHECKED_READ(other.sequence_token_) = SequenceToken();
+
+ return *this;
+}
+
+bool ThreadCheckerImpl::CalledOnValidThread(
+ std::unique_ptr<debug::StackTrace>* out_bound_at) const {
+ const bool has_thread_been_destroyed = ThreadLocalStorage::HasBeenDestroyed();
+
AutoLock auto_lock(lock_);
-#endif // defined(STARBOARD)
- EnsureAssigned();
+ return CalledOnValidThreadInternal(out_bound_at, has_thread_been_destroyed);
+}
- // Always return true when called from the task from which this
- // ThreadCheckerImpl was assigned to a thread.
- if (task_token_ == TaskToken::GetForCurrentThread())
+bool ThreadCheckerImpl::CalledOnValidThreadInternal(
+ std::unique_ptr<debug::StackTrace>* out_bound_at,
+ bool has_thread_been_destroyed) const {
+ // TaskToken/SequenceToken access thread-local storage. During destruction
+ // the state of thread-local storage is not guaranteed to be in a consistent
+ // state. Further, task-runner only installs the tokens when running a task.
+ if (!has_thread_been_destroyed) {
+ EnsureAssigned();
+
+ // Always return true when called from the task from which this
+ // ThreadCheckerImpl was assigned to a thread.
+ if (task_token_ == TaskToken::GetForCurrentThread())
+ return true;
+
+ // If this ThreadCheckerImpl is bound to a valid SequenceToken, it must be
+ // equal to the current SequenceToken and there must be a registered
+ // SingleThreadTaskRunner::CurrentDefaultHandle. Otherwise, the fact that
+ // the current task runs on the thread to which this ThreadCheckerImpl is
+ // bound is fortuitous.
+ if (sequence_token_.IsValid() &&
+ (sequence_token_ != SequenceToken::GetForCurrentThread() ||
+ !SingleThreadTaskRunner::HasCurrentDefault())) {
+ if (out_bound_at && bound_at_) {
+ *out_bound_at = std::make_unique<debug::StackTrace>(*bound_at_);
+ }
+ return false;
+ }
+ } else if (thread_id_.is_null()) {
+ // We're in tls destruction but the |thread_id_| hasn't been assigned yet.
+ // Assign it now. This doesn't call EnsureAssigned() as to do so while in
+ // tls destruction may result in the wrong TaskToken/SequenceToken.
+ if (g_log_thread_and_sequence_checker_binding)
+ bound_at_ = std::make_unique<debug::StackTrace>(size_t{10});
+ thread_id_ = PlatformThread::CurrentRef();
return true;
-
- // If this ThreadCheckerImpl is bound to a valid SequenceToken, it must be
- // equal to the current SequenceToken and there must be a registered
- // ThreadTaskRunnerHandle. Otherwise, the fact that the current task runs on
- // the thread to which this ThreadCheckerImpl is bound is fortuitous.
- if (sequence_token_.IsValid() &&
- (sequence_token_ != SequenceToken::GetForCurrentThread() ||
- !ThreadTaskRunnerHandle::IsSet())) {
- return false;
}
- return thread_id_ == PlatformThread::CurrentRef();
+ if (thread_id_ != PlatformThread::CurrentRef()) {
+ if (out_bound_at && bound_at_) {
+ *out_bound_at = std::make_unique<debug::StackTrace>(*bound_at_);
+ }
+ return false;
+ }
+ return true;
}
void ThreadCheckerImpl::DetachFromThread() {
-#if defined(STARBOARD)
- starboard::ScopedSpinLock lock(&members_lock_);
-#else // defined(STARBOARD)
AutoLock auto_lock(lock_);
-#endif // defined(STARBOARD)
+ bound_at_ = nullptr;
thread_id_ = PlatformThreadRef();
task_token_ = TaskToken();
sequence_token_ = SequenceToken();
}
+std::unique_ptr<debug::StackTrace> ThreadCheckerImpl::GetBoundAt() const {
+ if (!bound_at_)
+ return nullptr;
+ return std::make_unique<debug::StackTrace>(*bound_at_);
+}
+
void ThreadCheckerImpl::EnsureAssigned() const {
-#if defined(STARBOARD)
- SB_DCHECK(members_lock_ == starboard::kSpinLockStateAcquired);
-#else // defined(STARBOARD)
- lock_.AssertAcquired();
-#endif // defined(STARBOARD)
if (!thread_id_.is_null())
return;
-
+ if (g_log_thread_and_sequence_checker_binding)
+ bound_at_ = std::make_unique<debug::StackTrace>(size_t{10});
thread_id_ = PlatformThread::CurrentRef();
task_token_ = TaskToken::GetForCurrentThread();
sequence_token_ = SequenceToken::GetForCurrentThread();
diff --git a/base/threading/thread_checker_impl.h b/base/threading/thread_checker_impl.h
index b871ad8..0d1d011 100644
--- a/base/threading/thread_checker_impl.h
+++ b/base/threading/thread_checker_impl.h
@@ -1,18 +1,24 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_THREAD_CHECKER_IMPL_H_
#define BASE_THREADING_THREAD_CHECKER_IMPL_H_
+#include <memory>
+
#include "base/base_export.h"
-#include "base/compiler_specific.h"
#include "base/sequence_token.h"
#include "base/synchronization/lock.h"
-#include "base/threading/platform_thread.h"
-#include "starboard/common/spin_lock.h"
+#include "base/thread_annotations.h"
+#include "base/threading/platform_thread_ref.h"
namespace base {
+namespace debug {
+class StackTrace;
+}
+
+class SequenceCheckerImpl;
// Real implementation of ThreadChecker, for use in debug mode, or for temporary
// use in release mode (e.g. to CHECK on a threading issue seen only in the
@@ -20,33 +26,64 @@
//
// Note: You should almost always use the ThreadChecker class to get the right
// version for your build configuration.
-class BASE_EXPORT ThreadCheckerImpl {
+// Note: This is only a check, not a "lock". It is marked "LOCKABLE" only in
+// order to support thread_annotations.h.
+class LOCKABLE BASE_EXPORT ThreadCheckerImpl {
public:
+ static void EnableStackLogging();
+
ThreadCheckerImpl();
~ThreadCheckerImpl();
- bool CalledOnValidThread() const WARN_UNUSED_RESULT;
+ // Allow move construct/assign. This must be called on |other|'s associated
+ // thread and assignment can only be made into a ThreadCheckerImpl which is
+ // detached or already associated with the current thread. This isn't
+ // thread-safe (|this| and |other| shouldn't be in use while this move is
+ // performed). If the assignment was legal, the resulting ThreadCheckerImpl
+ // will be bound to the current thread and |other| will be detached.
+ ThreadCheckerImpl(ThreadCheckerImpl&& other);
+ ThreadCheckerImpl& operator=(ThreadCheckerImpl&& other);
+
+ // On returning false, if logging is enabled with EnableStackLogging() and
+ // `out_bound_at` is not null, this method allocates a StackTrace and returns
+ // it in the out-parameter, storing inside it the stack from where the failing
+ // ThreadChecker was bound to its thread.
+ [[nodiscard]] bool CalledOnValidThread(
+ std::unique_ptr<debug::StackTrace>* out_bound_at = nullptr) const
+ LOCKS_EXCLUDED(lock_);
// Changes the thread that is checked for in CalledOnValidThread. This may
// be useful when an object may be created on one thread and then used
// exclusively on another thread.
- void DetachFromThread();
+ void DetachFromThread() LOCKS_EXCLUDED(lock_);
private:
- void EnsureAssigned() const;
+ // This shares storage with SequenceCheckerImpl.
+ friend class SequenceCheckerImpl;
+
+ [[nodiscard]] bool CalledOnValidThreadInternal(
+ std::unique_ptr<debug::StackTrace>* out_bound_at,
+ bool has_thread_been_destroyed) const EXCLUSIVE_LOCKS_REQUIRED(lock_);
+
+ // Returns ownership of a pointer to StackTrace where the ThreadCheckerImpl
+ // was bound for debug logs, or nullptr if such logging was not enabled at
+ // the time.
+ std::unique_ptr<debug::StackTrace> GetBoundAt() const
+ EXCLUSIVE_LOCKS_REQUIRED(lock_);
+
+ void EnsureAssigned() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
// Members are mutable so that CalledOnValidThread() can set them.
-#if defined(STARBOARD)
- // Don't use a mutex since the number of mutexes is limited on some platforms.
- mutable SbAtomic32 members_lock_ = starboard::kSpinLockStateReleased;
-#else // defined(STARBOARD)
// Synchronizes access to all members.
mutable base::Lock lock_;
-#endif // defined(STARBOARD)
+
+ // The location where the ThreadChecker was bound to the current
+ // thread/task/sequence. Default-initialized with 0 frames until bound.
+ mutable std::unique_ptr<debug::StackTrace> bound_at_ GUARDED_BY(lock_);
// Thread on which CalledOnValidThread() may return true.
- mutable PlatformThreadRef thread_id_;
+ mutable PlatformThreadRef thread_id_ GUARDED_BY(lock_);
// TaskToken for which CalledOnValidThread() always returns true. This allows
// CalledOnValidThread() to return true when called multiple times from the
@@ -54,13 +91,16 @@
// (allowing usage of ThreadChecker objects on the stack in the scope of one-
// off tasks). Note: CalledOnValidThread() may return true even if the current
// TaskToken is not equal to this.
- mutable TaskToken task_token_;
+ mutable TaskToken task_token_ GUARDED_BY(lock_);
// SequenceToken for which CalledOnValidThread() may return true. Used to
- // ensure that CalledOnValidThread() doesn't return true for TaskScheduler
+ // ensure that CalledOnValidThread() doesn't return true for ThreadPool
// tasks that happen to run on the same thread but weren't posted to the same
// SingleThreadTaskRunner.
- mutable SequenceToken sequence_token_;
+ //
+ // Also used for SequenceCheckerImpl's CalledOnValidSequence(), as this shares
+ // storage. See SequenceCheckerImpl.
+ mutable SequenceToken sequence_token_ GUARDED_BY(lock_);
};
} // namespace base
diff --git a/base/threading/thread_checker_unittest.cc b/base/threading/thread_checker_unittest.cc
index 5fbbc52..b405bd8 100644
--- a/base/threading/thread_checker_unittest.cc
+++ b/base/threading/thread_checker_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,15 +6,16 @@
#include <memory>
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/macros.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/sequence_token.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/simple_thread.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/threading/thread_local.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -23,21 +24,22 @@
// A thread that runs a callback.
class RunCallbackThread : public SimpleThread {
public:
- explicit RunCallbackThread(const Closure& callback)
- : SimpleThread("RunCallbackThread"), callback_(callback) {}
+ explicit RunCallbackThread(OnceClosure callback)
+ : SimpleThread("RunCallbackThread"), callback_(std::move(callback)) {}
+
+ RunCallbackThread(const RunCallbackThread&) = delete;
+ RunCallbackThread& operator=(const RunCallbackThread&) = delete;
private:
// SimpleThread:
- void Run() override { callback_.Run(); }
+ void Run() override { std::move(callback_).Run(); }
- const Closure callback_;
-
- DISALLOW_COPY_AND_ASSIGN(RunCallbackThread);
+ OnceClosure callback_;
};
// Runs a callback on a new thread synchronously.
-void RunCallbackOnNewThreadSynchronously(const Closure& callback) {
- RunCallbackThread run_callback_thread(callback);
+void RunCallbackOnNewThreadSynchronously(OnceClosure callback) {
+ RunCallbackThread run_callback_thread(std::move(callback));
run_callback_thread.Start();
run_callback_thread.Join();
}
@@ -62,8 +64,9 @@
void ExpectNotCalledOnValidThreadWithSequenceTokenAndThreadTaskRunnerHandle(
ThreadCheckerImpl* thread_checker,
SequenceToken sequence_token) {
- ThreadTaskRunnerHandle thread_task_runner_handle(
- MakeRefCounted<TestSimpleTaskRunner>());
+ SingleThreadTaskRunner::CurrentDefaultHandle
+ single_thread_task_runner_current_default_handle(
+ MakeRefCounted<TestSimpleTaskRunner>());
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(sequence_token);
ExpectNotCalledOnValidThread(thread_checker);
@@ -78,8 +81,9 @@
TEST(ThreadCheckerTest,
AllowedSameThreadAndSequenceDifferentTasksWithThreadTaskRunnerHandle) {
- ThreadTaskRunnerHandle thread_task_runner_handle(
- MakeRefCounted<TestSimpleTaskRunner>());
+ SingleThreadTaskRunner::CurrentDefaultHandle
+ single_thread_task_runner_current_default_handle(
+ MakeRefCounted<TestSimpleTaskRunner>());
std::unique_ptr<ThreadCheckerImpl> thread_checker;
const SequenceToken sequence_token = SequenceToken::Create();
@@ -87,7 +91,7 @@
{
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(sequence_token);
- thread_checker.reset(new ThreadCheckerImpl);
+ thread_checker = std::make_unique<ThreadCheckerImpl>();
}
{
@@ -112,7 +116,7 @@
{
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
- thread_checker.reset(new ThreadCheckerImpl);
+ thread_checker = std::make_unique<ThreadCheckerImpl>();
}
{
@@ -125,12 +129,13 @@
TEST(ThreadCheckerTest, DisallowedDifferentThreadsNoSequenceToken) {
ThreadCheckerImpl thread_checker;
RunCallbackOnNewThreadSynchronously(
- Bind(&ExpectNotCalledOnValidThread, Unretained(&thread_checker)));
+ BindOnce(&ExpectNotCalledOnValidThread, Unretained(&thread_checker)));
}
TEST(ThreadCheckerTest, DisallowedDifferentThreadsSameSequence) {
- ThreadTaskRunnerHandle thread_task_runner_handle(
- MakeRefCounted<TestSimpleTaskRunner>());
+ SingleThreadTaskRunner::CurrentDefaultHandle
+ single_thread_task_runner_current_default_handle(
+ MakeRefCounted<TestSimpleTaskRunner>());
const SequenceToken sequence_token(SequenceToken::Create());
ScopedSetSequenceTokenForCurrentThread
@@ -138,7 +143,7 @@
ThreadCheckerImpl thread_checker;
EXPECT_TRUE(thread_checker.CalledOnValidThread());
- RunCallbackOnNewThreadSynchronously(Bind(
+ RunCallbackOnNewThreadSynchronously(BindOnce(
&ExpectNotCalledOnValidThreadWithSequenceTokenAndThreadTaskRunnerHandle,
Unretained(&thread_checker), sequence_token));
}
@@ -146,13 +151,14 @@
TEST(ThreadCheckerTest, DisallowedSameThreadDifferentSequence) {
std::unique_ptr<ThreadCheckerImpl> thread_checker;
- ThreadTaskRunnerHandle thread_task_runner_handle(
- MakeRefCounted<TestSimpleTaskRunner>());
+ SingleThreadTaskRunner::CurrentDefaultHandle
+ single_thread_task_runner_current_default_handle(
+ MakeRefCounted<TestSimpleTaskRunner>());
{
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
- thread_checker.reset(new ThreadCheckerImpl);
+ thread_checker = std::make_unique<ThreadCheckerImpl>();
}
{
@@ -173,14 +179,15 @@
// Verify that CalledOnValidThread() returns true when called on a different
// thread after a call to DetachFromThread().
RunCallbackOnNewThreadSynchronously(
- Bind(&ExpectCalledOnValidThread, Unretained(&thread_checker)));
+ BindOnce(&ExpectCalledOnValidThread, Unretained(&thread_checker)));
EXPECT_FALSE(thread_checker.CalledOnValidThread());
}
TEST(ThreadCheckerTest, DetachFromThreadWithSequenceToken) {
- ThreadTaskRunnerHandle thread_task_runner_handle(
- MakeRefCounted<TestSimpleTaskRunner>());
+ SingleThreadTaskRunner::CurrentDefaultHandle
+ single_thread_task_runner_current_default_handle(
+ MakeRefCounted<TestSimpleTaskRunner>());
ScopedSetSequenceTokenForCurrentThread
scoped_set_sequence_token_for_current_thread(SequenceToken::Create());
ThreadCheckerImpl thread_checker;
@@ -189,11 +196,117 @@
// Verify that CalledOnValidThread() returns true when called on a different
// thread after a call to DetachFromThread().
RunCallbackOnNewThreadSynchronously(
- Bind(&ExpectCalledOnValidThread, Unretained(&thread_checker)));
+ BindOnce(&ExpectCalledOnValidThread, Unretained(&thread_checker)));
EXPECT_FALSE(thread_checker.CalledOnValidThread());
}
+// Owns a ThreadCheckerImpl and asserts that CalledOnValidThread() is valid
+// in ~ThreadCheckerOwner.
+class ThreadCheckerOwner {
+ public:
+ explicit ThreadCheckerOwner(bool detach_from_thread) {
+ if (detach_from_thread)
+ checker_.DetachFromThread();
+ }
+
+ ThreadCheckerOwner(const ThreadCheckerOwner&) = delete;
+ ThreadCheckerOwner& operator=(const ThreadCheckerOwner&) = delete;
+
+ ~ThreadCheckerOwner() { EXPECT_TRUE(checker_.CalledOnValidThread()); }
+
+ private:
+ ThreadCheckerImpl checker_;
+};
+
+// Verifies ThreadCheckerImpl::CalledOnValidThread() returns true if called
+// during thread destruction.
+TEST(ThreadCheckerTest, CalledOnValidThreadFromThreadDestruction) {
+ ThreadLocalOwnedPointer<ThreadCheckerOwner> thread_local_owner;
+ RunCallbackOnNewThreadSynchronously(BindLambdaForTesting([&]() {
+ thread_local_owner.Set(std::make_unique<ThreadCheckerOwner>(false));
+ }));
+}
+
+// Variant of CalledOnValidThreadFromThreadDestruction that calls
+// ThreadCheckerImpl::DetachFromThread().
+TEST(ThreadCheckerTest, CalledOnValidThreadFromThreadDestructionDetached) {
+ ThreadLocalOwnedPointer<ThreadCheckerOwner> thread_local_owner;
+ RunCallbackOnNewThreadSynchronously(BindLambdaForTesting([&]() {
+ thread_local_owner.Set(std::make_unique<ThreadCheckerOwner>(true));
+ }));
+}
+
+TEST(ThreadCheckerTest, Move) {
+ ThreadCheckerImpl initial;
+ EXPECT_TRUE(initial.CalledOnValidThread());
+
+ ThreadCheckerImpl move_constructed(std::move(initial));
+ EXPECT_TRUE(move_constructed.CalledOnValidThread());
+
+ ThreadCheckerImpl move_assigned;
+ move_assigned = std::move(move_constructed);
+ EXPECT_TRUE(move_assigned.CalledOnValidThread());
+
+ // The two ThreadCheckerImpls moved from should be able to rebind to another
+ // thread.
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectCalledOnValidThread, Unretained(&initial)));
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectCalledOnValidThread, Unretained(&move_constructed)));
+
+ // But the latest one shouldn't be able to run on another thread.
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectNotCalledOnValidThread, Unretained(&move_assigned)));
+
+ EXPECT_TRUE(move_assigned.CalledOnValidThread());
+}
+
+TEST(ThreadCheckerTest, MoveAssignIntoDetached) {
+ ThreadCheckerImpl initial;
+
+ ThreadCheckerImpl move_assigned;
+ move_assigned.DetachFromThread();
+ move_assigned = std::move(initial);
+
+ // |initial| is detached after move.
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectCalledOnValidThread, Unretained(&initial)));
+
+ // |move_assigned| should be associated with the main thread.
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectNotCalledOnValidThread, Unretained(&move_assigned)));
+
+ EXPECT_TRUE(move_assigned.CalledOnValidThread());
+}
+
+TEST(ThreadCheckerTest, MoveFromDetachedRebinds) {
+ ThreadCheckerImpl initial;
+ initial.DetachFromThread();
+
+ ThreadCheckerImpl moved_into(std::move(initial));
+
+ // |initial| is still detached after move.
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectCalledOnValidThread, Unretained(&initial)));
+
+ // |moved_into| is bound to the current thread as part of the move.
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectNotCalledOnValidThread, Unretained(&moved_into)));
+ EXPECT_TRUE(moved_into.CalledOnValidThread());
+}
+
+TEST(ThreadCheckerTest, MoveOffThreadBanned) {
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+
+ ThreadCheckerImpl other_thread;
+ other_thread.DetachFromThread();
+ RunCallbackOnNewThreadSynchronously(
+ BindOnce(&ExpectCalledOnValidThread, Unretained(&other_thread)));
+
+ EXPECT_DCHECK_DEATH(ThreadCheckerImpl main_thread(std::move(other_thread)));
+}
+
namespace {
// This fixture is a helper for unit testing the thread checker macros as it is
@@ -205,41 +318,41 @@
public:
ThreadCheckerMacroTest() = default;
+ ThreadCheckerMacroTest(const ThreadCheckerMacroTest&) = delete;
+ ThreadCheckerMacroTest& operator=(const ThreadCheckerMacroTest&) = delete;
+
void ExpectDeathOnOtherThread() {
#if DCHECK_IS_ON()
- EXPECT_DCHECK_DEATH({ DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_); });
+ EXPECT_DCHECK_DEATH({ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); });
#else
// Happily no-ops on non-dcheck builds.
- DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#endif
}
void ExpectNoDeathOnOtherThreadAfterDetach() {
- DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_);
- DCHECK_CALLED_ON_VALID_THREAD(my_thread_checker_)
- << "Make sure it compiles when DCHECK is off";
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
}
protected:
- THREAD_CHECKER(my_thread_checker_);
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ThreadCheckerMacroTest);
+ THREAD_CHECKER(thread_checker_);
};
} // namespace
TEST_F(ThreadCheckerMacroTest, Macros) {
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
+
THREAD_CHECKER(my_thread_checker);
- RunCallbackOnNewThreadSynchronously(Bind(
+ RunCallbackOnNewThreadSynchronously(BindOnce(
&ThreadCheckerMacroTest::ExpectDeathOnOtherThread, Unretained(this)));
- DETACH_FROM_THREAD(my_thread_checker_);
+ DETACH_FROM_THREAD(thread_checker_);
RunCallbackOnNewThreadSynchronously(
- Bind(&ThreadCheckerMacroTest::ExpectNoDeathOnOtherThreadAfterDetach,
- Unretained(this)));
+ BindOnce(&ThreadCheckerMacroTest::ExpectNoDeathOnOtherThreadAfterDetach,
+ Unretained(this)));
}
} // namespace base
diff --git a/base/threading/thread_collision_warner.cc b/base/threading/thread_collision_warner.cc
index 547e11c..ee055c0 100644
--- a/base/threading/thread_collision_warner.cc
+++ b/base/threading/thread_collision_warner.cc
@@ -1,10 +1,12 @@
-// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright 2010 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread_collision_warner.h"
-#include "base/logging.h"
+#include <ostream>
+
+#include "base/notreached.h"
#include "base/threading/platform_thread.h"
namespace base {
diff --git a/base/threading/thread_collision_warner.h b/base/threading/thread_collision_warner.h
index fcd1d71..a11ac77 100644
--- a/base/threading/thread_collision_warner.h
+++ b/base/threading/thread_collision_warner.h
@@ -1,16 +1,14 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_THREAD_COLLISION_WARNER_H_
#define BASE_THREADING_THREAD_COLLISION_WARNER_H_
-#include <memory>
-
#include "base/atomicops.h"
#include "base/base_export.h"
#include "base/compiler_specific.h"
-#include "base/macros.h"
+#include "base/memory/raw_ptr.h"
// A helper class alongside macros to be used to verify assumptions about thread
// safety of a class.
@@ -20,7 +18,7 @@
//
// In this case the macro DFAKE_SCOPED_LOCK has to be
// used, it checks that if a thread is inside the push/pop then
-// no one else is still inside the pop/push
+// noone else is still inside the pop/push
//
// class NonThreadSafeQueue {
// public:
@@ -39,7 +37,7 @@
//
// In this case the macro DFAKE_SCOPED_RECURSIVE_LOCK
// has to be used, it checks that if a thread is inside the push/pop
-// then no one else is still inside the pop/push
+// then noone else is still inside the pop/push
//
// class NonThreadSafeQueue {
// public:
@@ -79,7 +77,7 @@
// };
//
//
-// Example: Class that has to be constructed/destroyed on same thread, it has
+// Example: Class that has to be contructed/destroyed on same thread, it has
// a "shareable" method (with external synchronization) and a not
// shareable method (even with external synchronization).
//
@@ -98,22 +96,30 @@
// DFAKE_MUTEX(shareable_section_);
// };
-#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)
+#if !defined(NDEBUG)
+
+#define DFAKE_UNIQUE_VARIABLE_CONCAT(a, b) a##b
+// CONCAT1 provides extra level of indirection so that __LINE__ macro expands.
+#define DFAKE_UNIQUE_VARIABLE_CONCAT1(a, b) DFAKE_UNIQUE_VARIABLE_CONCAT(a, b)
+#define DFAKE_UNIQUE_VARIABLE_NAME(a) DFAKE_UNIQUE_VARIABLE_CONCAT1(a, __LINE__)
// Defines a class member that acts like a mutex. It is used only as a
// verification tool.
-#define DFAKE_MUTEX(obj) mutable base::ThreadCollisionWarner obj
+#define DFAKE_MUTEX(obj) \
+ mutable base::ThreadCollisionWarner obj
// Asserts the call is never called simultaneously in two threads. Used at
// member function scope.
-#define DFAKE_SCOPED_LOCK(obj) \
- base::ThreadCollisionWarner::ScopedCheck s_check_##obj(&obj)
+#define DFAKE_SCOPED_LOCK(obj) \
+ base::ThreadCollisionWarner::ScopedCheck DFAKE_UNIQUE_VARIABLE_NAME( \
+ s_check_)(&obj)
// Asserts the call is never called simultaneously in two threads. Used at
// member function scope. Same as DFAKE_SCOPED_LOCK but allows recursive locks.
-#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) \
- base::ThreadCollisionWarner::ScopedRecursiveCheck sr_check_##obj(&obj)
+#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) \
+ base::ThreadCollisionWarner::ScopedRecursiveCheck \
+ DFAKE_UNIQUE_VARIABLE_NAME(sr_check)(&obj)
// Asserts the code is always executed in the same thread.
#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) \
- base::ThreadCollisionWarner::Check check_##obj(&obj)
+ base::ThreadCollisionWarner::Check DFAKE_UNIQUE_VARIABLE_NAME(check_)(&obj)
#else
@@ -144,9 +150,14 @@
public:
// The parameter asserter is there only for test purpose
explicit ThreadCollisionWarner(AsserterBase* asserter = new DCheckAsserter())
- : valid_thread_id_(0), counter_(0), asserter_(asserter) {}
+ : valid_thread_id_(0),
+ counter_(0),
+ asserter_(asserter) {}
- ~ThreadCollisionWarner() { delete asserter_; }
+ ThreadCollisionWarner(const ThreadCollisionWarner&) = delete;
+ ThreadCollisionWarner& operator=(const ThreadCollisionWarner&) = delete;
+
+ ~ThreadCollisionWarner() { asserter_.ClearAndDelete(); }
// This class is meant to be used through the macro
// DFAKE_SCOPED_LOCK_THREAD_LOCKED
@@ -155,32 +166,38 @@
// from one thread
class BASE_EXPORT Check {
public:
- explicit Check(ThreadCollisionWarner* warner) : warner_(warner) {
+ explicit Check(ThreadCollisionWarner* warner)
+ : warner_(warner) {
warner_->EnterSelf();
}
+ Check(const Check&) = delete;
+ Check& operator=(const Check&) = delete;
+
~Check() = default;
private:
- ThreadCollisionWarner* warner_;
-
- DISALLOW_COPY_AND_ASSIGN(Check);
+ raw_ptr<ThreadCollisionWarner> warner_;
};
// This class is meant to be used through the macro
// DFAKE_SCOPED_LOCK
class BASE_EXPORT ScopedCheck {
public:
- explicit ScopedCheck(ThreadCollisionWarner* warner) : warner_(warner) {
+ explicit ScopedCheck(ThreadCollisionWarner* warner)
+ : warner_(warner) {
warner_->Enter();
}
- ~ScopedCheck() { warner_->Leave(); }
+ ScopedCheck(const ScopedCheck&) = delete;
+ ScopedCheck& operator=(const ScopedCheck&) = delete;
+
+ ~ScopedCheck() {
+ warner_->Leave();
+ }
private:
- ThreadCollisionWarner* warner_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedCheck);
+ raw_ptr<ThreadCollisionWarner> warner_;
};
// This class is meant to be used through the macro
@@ -192,12 +209,15 @@
warner_->EnterSelf();
}
- ~ScopedRecursiveCheck() { warner_->Leave(); }
+ ScopedRecursiveCheck(const ScopedRecursiveCheck&) = delete;
+ ScopedRecursiveCheck& operator=(const ScopedRecursiveCheck&) = delete;
+
+ ~ScopedRecursiveCheck() {
+ warner_->Leave();
+ }
private:
- ThreadCollisionWarner* warner_;
-
- DISALLOW_COPY_AND_ASSIGN(ScopedRecursiveCheck);
+ raw_ptr<ThreadCollisionWarner> warner_;
};
private:
@@ -223,9 +243,7 @@
// Here only for class unit tests purpose, during the test I need to not
// DCHECK but notify the collision with something else.
- AsserterBase* asserter_;
-
- DISALLOW_COPY_AND_ASSIGN(ThreadCollisionWarner);
+ raw_ptr<AsserterBase> asserter_;
};
} // namespace base
diff --git a/base/threading/thread_collision_warner_unittest.cc b/base/threading/thread_collision_warner_unittest.cc
index cd56768..ffd9c5a 100644
--- a/base/threading/thread_collision_warner_unittest.cc
+++ b/base/threading/thread_collision_warner_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -7,16 +7,12 @@
#include <memory>
#include "base/compiler_specific.h"
-#include "base/macros.h"
+#include "base/memory/raw_ptr.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
-// '' : local class member function does not have a body
-MSVC_PUSH_DISABLE_WARNING(4822)
-
-
#if defined(NDEBUG)
// Would cause a memory leak otherwise.
@@ -132,6 +128,9 @@
: push_pop_(asserter) {
}
+ NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
+ NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
+
void push(int value) {
DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_);
}
@@ -143,8 +142,6 @@
private:
DFAKE_MUTEX(push_pop_);
-
- DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue);
};
class QueueUser : public base::DelegateSimpleThread::Delegate {
@@ -157,7 +154,7 @@
}
private:
- NonThreadSafeQueue* queue_;
+ raw_ptr<NonThreadSafeQueue> queue_;
};
AssertReporter* local_reporter = new AssertReporter();
@@ -188,9 +185,12 @@
: push_pop_(asserter) {
}
+ NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
+ NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
+
void push(int value) {
DFAKE_SCOPED_LOCK(push_pop_);
- base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(5));
+ base::PlatformThread::Sleep(base::Seconds(5));
}
int pop() {
@@ -200,8 +200,6 @@
private:
DFAKE_MUTEX(push_pop_);
-
- DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue);
};
class QueueUser : public base::DelegateSimpleThread::Delegate {
@@ -214,7 +212,7 @@
}
private:
- NonThreadSafeQueue* queue_;
+ raw_ptr<NonThreadSafeQueue> queue_;
};
AssertReporter* local_reporter = new AssertReporter();
@@ -245,9 +243,12 @@
: push_pop_(asserter) {
}
+ NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
+ NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
+
void push(int value) {
DFAKE_SCOPED_LOCK(push_pop_);
- base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2));
+ base::PlatformThread::Sleep(base::Seconds(2));
}
int pop() {
@@ -257,8 +258,6 @@
private:
DFAKE_MUTEX(push_pop_);
-
- DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue);
};
// This time the QueueUser class protects the non thread safe queue with
@@ -279,8 +278,8 @@
}
}
private:
- NonThreadSafeQueue* queue_;
- base::Lock* lock_;
+ raw_ptr<NonThreadSafeQueue> queue_;
+ raw_ptr<base::Lock> lock_;
};
AssertReporter* local_reporter = new AssertReporter();
@@ -313,10 +312,13 @@
: push_pop_(asserter) {
}
+ NonThreadSafeQueue(const NonThreadSafeQueue&) = delete;
+ NonThreadSafeQueue& operator=(const NonThreadSafeQueue&) = delete;
+
void push(int) {
DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_);
bar();
- base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(2));
+ base::PlatformThread::Sleep(base::Seconds(2));
}
int pop() {
@@ -330,8 +332,6 @@
private:
DFAKE_MUTEX(push_pop_);
-
- DISALLOW_COPY_AND_ASSIGN(NonThreadSafeQueue);
};
// This time the QueueUser class protects the non thread safe queue with
@@ -356,8 +356,8 @@
}
}
private:
- NonThreadSafeQueue* queue_;
- base::Lock* lock_;
+ raw_ptr<NonThreadSafeQueue> queue_;
+ raw_ptr<base::Lock> lock_;
};
AssertReporter* local_reporter = new AssertReporter();
diff --git a/base/threading/thread_id_name_manager.cc b/base/threading/thread_id_name_manager.cc
index 46e1c56..7701cf0 100644
--- a/base/threading/thread_id_name_manager.cc
+++ b/base/threading/thread_id_name_manager.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -7,13 +7,20 @@
#include <stdlib.h>
#include <string.h>
-#include "base/logging.h"
+#include "base/check.h"
+#include "base/containers/contains.h"
+#include "base/containers/cxx20_erase.h"
#include "base/memory/singleton.h"
-#include "base/no_destructor.h"
#include "base/strings/string_util.h"
-#include "base/threading/thread_local.h"
-#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
-#include "starboard/types.h"
+#include "base/trace_event/heap_profiler_allocation_context_tracker.h" // no-presubmit-check
+#include "third_party/abseil-cpp/absl/base/attributes.h"
+
+#if defined(STARBOARD)
+#include <pthread.h>
+
+#include "base/check_op.h"
+#include "starboard/thread.h"
+#endif
namespace base {
namespace {
@@ -21,12 +28,32 @@
static const char kDefaultName[] = "";
static std::string* g_default_name;
-ThreadLocalStorage::Slot& GetThreadNameTLS() {
- static base::NoDestructor<base::ThreadLocalStorage::Slot> thread_name_tls;
- return *thread_name_tls;
+#if defined(STARBOARD)
+ABSL_CONST_INIT pthread_once_t s_once_flag = PTHREAD_ONCE_INIT;
+ABSL_CONST_INIT pthread_key_t s_thread_local_key = 0;
+
+void InitThreadLocalKey() {
+ int res = pthread_key_create(&s_thread_local_key , NULL);
+ DCHECK(res == 0);
}
+
+void EnsureThreadLocalKeyInited() {
+ pthread_once(&s_once_flag, InitThreadLocalKey);
}
+const char* GetThreadName() {
+ EnsureThreadLocalKeyInited();
+ const char* thread_name = static_cast<const char*>(
+ pthread_getspecific(s_thread_local_key));
+ return !!thread_name ? thread_name : kDefaultName;
+}
+#else
+ABSL_CONST_INIT thread_local const char* thread_name = kDefaultName;
+#endif
+}
+
+ThreadIdNameManager::Observer::~Observer() = default;
+
ThreadIdNameManager::ThreadIdNameManager()
: main_process_name_(nullptr), main_process_id_(kInvalidThreadId) {
g_default_name = new std::string(kDefaultName);
@@ -54,9 +81,16 @@
name_to_interned_name_[kDefaultName];
}
-void ThreadIdNameManager::InstallSetNameCallback(SetNameCallback callback) {
+void ThreadIdNameManager::AddObserver(Observer* obs) {
AutoLock locked(lock_);
- set_name_callback_ = std::move(callback);
+ DCHECK(!base::Contains(observers_, obs));
+ observers_.push_back(obs);
+}
+
+void ThreadIdNameManager::RemoveObserver(Observer* obs) {
+ AutoLock locked(lock_);
+ DCHECK(base::Contains(observers_, obs));
+ base::Erase(observers_, obs);
}
void ThreadIdNameManager::SetName(const std::string& name) {
@@ -74,10 +108,14 @@
auto id_to_handle_iter = thread_id_to_handle_.find(id);
- GetThreadNameTLS().Set(const_cast<char*>(leaked_str->c_str()));
- if (set_name_callback_) {
- set_name_callback_.Run(leaked_str->c_str());
- }
+#if defined(STARBOARD)
+ EnsureThreadLocalKeyInited();
+ pthread_setspecific(s_thread_local_key, const_cast<char*>(leaked_str->c_str()));
+#else
+ thread_name = leaked_str->c_str();
+#endif
+ for (Observer* obs : observers_)
+ obs->OnThreadNameChanged(leaked_str->c_str());
// The main thread of a process will not be created as a Thread object which
// means there is no PlatformThreadHandler registered.
@@ -114,8 +152,11 @@
}
const char* ThreadIdNameManager::GetNameForCurrentThread() {
- const char* name = reinterpret_cast<const char*>(GetThreadNameTLS().Get());
- return name ? name : kDefaultName;
+#if defined(STARBOARD)
+ return GetThreadName();
+#else
+ return thread_name;
+#endif
}
void ThreadIdNameManager::RemoveName(PlatformThreadHandle::Handle handle,
@@ -136,4 +177,15 @@
thread_id_to_handle_.erase(id_to_handle_iter);
}
+std::vector<PlatformThreadId> ThreadIdNameManager::GetIds() {
+ AutoLock locked(lock_);
+
+ std::vector<PlatformThreadId> ids;
+ ids.reserve(thread_id_to_handle_.size());
+ for (const auto& iter : thread_id_to_handle_)
+ ids.push_back(iter.first);
+
+ return ids;
+}
+
} // namespace base
diff --git a/base/threading/thread_id_name_manager.h b/base/threading/thread_id_name_manager.h
index f17dc1a..85063c8 100644
--- a/base/threading/thread_id_name_manager.h
+++ b/base/threading/thread_id_name_manager.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -7,10 +7,11 @@
#include <map>
#include <string>
+#include <vector>
#include "base/base_export.h"
-#include "base/callback.h"
-#include "base/macros.h"
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
@@ -23,16 +24,29 @@
public:
static ThreadIdNameManager* GetInstance();
+ ThreadIdNameManager(const ThreadIdNameManager&) = delete;
+ ThreadIdNameManager& operator=(const ThreadIdNameManager&) = delete;
+
static const char* GetDefaultInternedString();
+ class BASE_EXPORT Observer {
+ public:
+ virtual ~Observer();
+
+ // Called on the thread whose name is changing, immediately after the name
+ // is set. |name| is a pointer to a C string that is guaranteed to remain
+ // valid for the duration of the process.
+ //
+ // NOTE: Will be called while ThreadIdNameManager's lock is held, so don't
+ // call back into it.
+ virtual void OnThreadNameChanged(const char* name) = 0;
+ };
+
// Register the mapping between a thread |id| and |handle|.
void RegisterThread(PlatformThreadHandle::Handle handle, PlatformThreadId id);
- // The callback is called on the thread, immediately after the name is set.
- // |name| is a pointer to a C string that is guaranteed to remain valid for
- // the duration of the process.
- using SetNameCallback = base::RepeatingCallback<void(const char* name)>;
- void InstallSetNameCallback(SetNameCallback callback);
+ void AddObserver(Observer*);
+ void RemoveObserver(Observer*);
// Set the name for the current thread.
void SetName(const std::string& name);
@@ -46,6 +60,10 @@
// Remove the name for the given id.
void RemoveName(PlatformThreadHandle::Handle handle, PlatformThreadId id);
+ // Return all registered thread ids (note that this doesn't include the main
+ // thread id).
+ std::vector<PlatformThreadId> GetIds();
+
private:
friend struct DefaultSingletonTraits<ThreadIdNameManager>;
@@ -67,12 +85,12 @@
ThreadHandleToInternedNameMap thread_handle_to_interned_name_;
// Treat the main process specially as there is no PlatformThreadHandle.
- std::string* main_process_name_;
+ raw_ptr<std::string> main_process_name_;
PlatformThreadId main_process_id_;
- SetNameCallback set_name_callback_;
-
- DISALLOW_COPY_AND_ASSIGN(ThreadIdNameManager);
+ // There's no point using a base::ObserverList behind a lock, so we just use
+ // an std::vector instead.
+ std::vector<Observer*> observers_;
};
} // namespace base
diff --git a/base/threading/thread_id_name_manager_unittest.cc b/base/threading/thread_id_name_manager_unittest.cc
index 350dc0f..b61131e 100644
--- a/base/threading/thread_id_name_manager_unittest.cc
+++ b/base/threading/thread_id_name_manager_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
diff --git a/base/threading/thread_local.h b/base/threading/thread_local.h
index cad9add..a32e671 100644
--- a/base/threading/thread_local.h
+++ b/base/threading/thread_local.h
@@ -1,98 +1,70 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// WARNING: Thread local storage is a bit tricky to get right. Please make sure
// that this is really the proper solution for what you're trying to achieve.
// Don't prematurely optimize, most likely you can just use a Lock.
-//
-// These classes implement a wrapper around ThreadLocalStorage::Slot. On
-// construction, they will allocate a TLS slot, and free the TLS slot on
-// destruction. No memory management (creation or destruction) is handled. This
-// means for uses of ThreadLocalPointer, you must correctly manage the memory
-// yourself, these classes will not destroy the pointer for you. There are no
-// at-thread-exit actions taken by these classes.
-//
-// ThreadLocalPointer<Type> wraps a Type*. It performs no creation or
-// destruction, so memory management must be handled elsewhere. The first call
-// to Get() on a thread will return NULL. You can update the pointer with a call
-// to Set().
-//
-// ThreadLocalBoolean wraps a bool. It will default to false if it has never
-// been set otherwise with Set().
-//
-// Thread Safety: An instance of ThreadLocalStorage is completely thread safe
-// once it has been created. If you want to dynamically create an instance, you
-// must of course properly deal with safety and race conditions. This means a
-// function-level static initializer is generally inappropiate.
-//
-// In Android, the system TLS is limited.
-//
-// Example usage:
-// // My class is logically attached to a single thread. We cache a pointer
-// // on the thread it was created on, so we can implement current().
-// MyClass::MyClass() {
-// DCHECK(Singleton<ThreadLocalPointer<MyClass> >::get()->Get() == NULL);
-// Singleton<ThreadLocalPointer<MyClass> >::get()->Set(this);
-// }
-//
-// MyClass::~MyClass() {
-// DCHECK(Singleton<ThreadLocalPointer<MyClass> >::get()->Get() != NULL);
-// Singleton<ThreadLocalPointer<MyClass> >::get()->Set(NULL);
-// }
-//
-// // Return the current MyClass associated with the calling thread, can be
-// // NULL if there isn't a MyClass associated.
-// MyClass* MyClass::current() {
-// return Singleton<ThreadLocalPointer<MyClass> >::get()->Get();
-// }
#ifndef BASE_THREADING_THREAD_LOCAL_H_
#define BASE_THREADING_THREAD_LOCAL_H_
-#include "base/macros.h"
+#include <memory>
+
+#include "base/dcheck_is_on.h"
+#include "base/memory/ptr_util.h"
+#include "base/threading/thread_local_internal.h"
#include "base/threading/thread_local_storage.h"
namespace base {
-template <typename Type>
-class ThreadLocalPointer {
+// `thread_local` is only allowed for trivially-destructible types (see
+// //styleguide/c++/c++.md#thread_local-variables). This class provides
+// thread-scoped management of non-trivially-destructible types. Pointers handed
+// to it are owned and automatically deleted during their associated thread's
+// exit phase (or when replaced if Set() is invoked multiple times on the same
+// thread).
+//
+// The ThreadLocalOwnedPointer instance itself can only be destroyed when no
+// threads, other than the one it is destroyed on, have remaining state set in
+// it. Typically this means that ThreadLocalOwnedPointer instances are held in
+// static storage or at the very least only recycled in the single-threaded
+// phase between tests in the same process.
+#if DCHECK_IS_ON()
+template <typename T>
+using ThreadLocalOwnedPointer = internal::CheckedThreadLocalOwnedPointer<T>;
+#else // DCHECK_IS_ON()
+template <typename T>
+class ThreadLocalOwnedPointer {
public:
- ThreadLocalPointer() = default;
- ~ThreadLocalPointer() = default;
+ ThreadLocalOwnedPointer() = default;
- Type* Get() {
- return static_cast<Type*>(slot_.Get());
+ ThreadLocalOwnedPointer(const ThreadLocalOwnedPointer&) = delete;
+ ThreadLocalOwnedPointer& operator=(const ThreadLocalOwnedPointer&) = delete;
+
+ ~ThreadLocalOwnedPointer() {
+ // Assume that this thread is the only one with potential state left. This
+ // is verified in ~CheckedThreadLocalOwnedPointer().
+ Set(nullptr);
}
- void Set(Type* ptr) {
- slot_.Set(const_cast<void*>(static_cast<const void*>(ptr)));
+ T* Get() const { return static_cast<T*>(slot_.Get()); }
+
+ // Sets a new value, returns the old.
+ std::unique_ptr<T> Set(std::unique_ptr<T> ptr) {
+ auto existing = WrapUnique(Get());
+ slot_.Set(const_cast<void*>(static_cast<const void*>(ptr.release())));
+ return existing;
}
+ T& operator*() { return *Get(); }
+
private:
- ThreadLocalStorage::Slot slot_;
+ static void DeleteTlsPtr(void* ptr) { delete static_cast<T*>(ptr); }
- DISALLOW_COPY_AND_ASSIGN(ThreadLocalPointer<Type>);
+ ThreadLocalStorage::Slot slot_{&DeleteTlsPtr};
};
-
-class ThreadLocalBoolean {
- public:
- ThreadLocalBoolean() = default;
- ~ThreadLocalBoolean() = default;
-
- bool Get() {
- return tlp_.Get() != nullptr;
- }
-
- void Set(bool val) {
- tlp_.Set(val ? this : nullptr);
- }
-
- private:
- ThreadLocalPointer<void> tlp_;
-
- DISALLOW_COPY_AND_ASSIGN(ThreadLocalBoolean);
-};
+#endif // DCHECK_IS_ON()
} // namespace base
diff --git a/base/threading/thread_local_internal.h b/base/threading/thread_local_internal.h
new file mode 100644
index 0000000..ed99410
--- /dev/null
+++ b/base/threading/thread_local_internal.h
@@ -0,0 +1,97 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_THREAD_LOCAL_INTERNAL_H_
+#define BASE_THREADING_THREAD_LOCAL_INTERNAL_H_
+
+#include "base/dcheck_is_on.h"
+
+#if DCHECK_IS_ON()
+
+#include <atomic>
+#include <memory>
+#include <ostream>
+
+#include "base/check_op.h"
+#include "base/memory/raw_ptr.h"
+#include "base/threading/thread_local_storage.h"
+
+namespace base {
+namespace internal {
+
+// A version of ThreadLocalOwnedPointer which verifies that it's only destroyed
+// when no threads, other than the one it is destroyed on, have remaining state
+// set in it. A ThreadLocalOwnedPointer instance being destroyed too early would
+// result in leaks per unregistering the TLS slot (and thus the DeleteTlsPtr
+// hook).
+template <typename T>
+class CheckedThreadLocalOwnedPointer {
+ public:
+ CheckedThreadLocalOwnedPointer() = default;
+
+ CheckedThreadLocalOwnedPointer<T>(const CheckedThreadLocalOwnedPointer<T>&) =
+ delete;
+ CheckedThreadLocalOwnedPointer<T>& operator=(
+ const CheckedThreadLocalOwnedPointer<T>&) = delete;
+
+ ~CheckedThreadLocalOwnedPointer() {
+ Set(nullptr);
+
+ DCHECK_EQ(num_assigned_threads_.load(std::memory_order_relaxed), 0)
+ << "Memory leak: Must join all threads or release all associated "
+ "thread-local slots before ~ThreadLocalOwnedPointer";
+ }
+
+ T* Get() const {
+ PtrTracker* const ptr_tracker = static_cast<PtrTracker*>(slot_.Get());
+ return ptr_tracker ? ptr_tracker->ptr_.get() : nullptr;
+ }
+
+ std::unique_ptr<T> Set(std::unique_ptr<T> ptr) {
+ std::unique_ptr<T> existing_ptr;
+ auto existing_tracker = static_cast<PtrTracker*>(slot_.Get());
+ if (existing_tracker) {
+ existing_ptr = std::move(existing_tracker->ptr_);
+ delete existing_tracker;
+ }
+
+ if (ptr)
+ slot_.Set(new PtrTracker(this, std::move(ptr)));
+ else
+ slot_.Set(nullptr);
+
+ return existing_ptr;
+ }
+
+ T& operator*() { return *Get(); }
+
+ private:
+ struct PtrTracker {
+ public:
+ PtrTracker(CheckedThreadLocalOwnedPointer<T>* outer, std::unique_ptr<T> ptr)
+ : outer_(outer), ptr_(std::move(ptr)) {
+ outer_->num_assigned_threads_.fetch_add(1, std::memory_order_relaxed);
+ }
+
+ ~PtrTracker() {
+ outer_->num_assigned_threads_.fetch_sub(1, std::memory_order_relaxed);
+ }
+
+ const raw_ptr<CheckedThreadLocalOwnedPointer<T>> outer_;
+ std::unique_ptr<T> ptr_;
+ };
+
+ static void DeleteTlsPtr(void* ptr) { delete static_cast<PtrTracker*>(ptr); }
+
+ ThreadLocalStorage::Slot slot_{&DeleteTlsPtr};
+
+ std::atomic_int num_assigned_threads_{0};
+};
+
+} // namespace internal
+} // namespace base
+
+#endif // DCHECK_IS_ON()
+
+#endif // BASE_THREADING_THREAD_LOCAL_INTERNAL_H_
diff --git a/base/threading/thread_local_storage.cc b/base/threading/thread_local_storage.cc
index f1d5324..a0ae20d 100644
--- a/base/threading/thread_local_storage.cc
+++ b/base/threading/thread_local_storage.cc
@@ -1,14 +1,24 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread_local_storage.h"
-#include "base/atomicops.h"
-#include "base/logging.h"
+#include <algorithm>
+#include <atomic>
+#include <cstring>
+
+#include "base/check_op.h"
+#include "base/compiler_specific.h"
+#include "base/memory/raw_ptr_exclusion.h"
+#include "base/notreached.h"
#include "base/synchronization/lock.h"
#include "build/build_config.h"
-#include "starboard/memory.h"
+
+#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)
+#include <pthread.h>
+#include <type_traits>
+#endif
using base::internal::PlatformThreadLocalStorage;
@@ -67,20 +77,21 @@
// Chromium consumers.
// g_native_tls_key is the one native TLS that we use. It stores our table.
-#if defined(STARBOARD)
-base::subtle::AtomicWord g_native_tls_key =
- reinterpret_cast<base::subtle::AtomicWord>(
- PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES);
-#else
-base::subtle::Atomic32 g_native_tls_key =
- PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES;
-#endif
-// The OS TLS slot has three states:
+std::atomic<PlatformThreadLocalStorage::TLSKey> g_native_tls_key{
+ PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES};
+
+// The OS TLS slot has the following states. The TLS slot's lower 2 bits contain
+// the state, the upper bits the TlsVectorEntry*.
// * kUninitialized: Any call to Slot::Get()/Set() will create the base
-// per-thread TLS state. On POSIX, kUninitialized must be 0.
-// * [Memory Address]: Raw pointer to the base per-thread TLS state.
-// * kDestroyed: The base per-thread TLS state has been freed.
+// per-thread TLS state. kUninitialized must be null.
+// * kInUse: value has been created and is in use.
+// * kDestroying: Set when the thread is exiting prior to deleting any of the
+// values stored in the TlsVectorEntry*. This state is necessary so that
+// sequence/task checks won't be done while in the process of deleting the
+// tls entries (see comments in SequenceCheckerImpl for more details).
+// * kDestroyed: All of the values in the vector have been deallocated and
+// the TlsVectorEntry has been deleted.
//
// Final States:
// * Windows: kDestroyed. Windows does not iterate through the OS TLS to clean
@@ -108,15 +119,32 @@
// through the 2-pass destruction system again. Everything should just
// work (TM).
-// The consumers of kUninitialized and kDestroyed expect void*, since that's
-// what the API exposes on both POSIX and Windows.
-void* const kUninitialized = nullptr;
+// The state of the tls-entry.
+enum class TlsVectorState {
+ kUninitialized = 0,
-// A sentinel value to indicate that the TLS system has been destroyed.
-void* const kDestroyed = reinterpret_cast<void*>(1);
+ // In the process of destroying the entries in the vector.
+ kDestroying,
+
+ // All of the entries and the vector has been destroyed.
+ kDestroyed,
+
+ // The vector has been initialized and is in use.
+ kInUse,
+
+ kMaxValue = kInUse
+};
+
+// Bit-mask used to store TlsVectorState.
+constexpr uintptr_t kVectorStateBitMask = 3;
+static_assert(static_cast<int>(TlsVectorState::kMaxValue) <=
+ kVectorStateBitMask,
+ "number of states must fit in header");
+static_assert(static_cast<int>(TlsVectorState::kUninitialized) == 0,
+ "kUninitialized must be null");
// The maximum number of slots in our thread local storage stack.
-constexpr int kThreadLocalStorageSize = 256;
+constexpr size_t kThreadLocalStorageSize = 256;
enum TlsStatus {
FREE,
@@ -126,11 +154,18 @@
struct TlsMetadata {
TlsStatus status;
base::ThreadLocalStorage::TLSDestructorFunc destructor;
+ // Incremented every time a slot is reused. Used to detect reuse of slots.
uint32_t version;
+ // Tracks slot creation order. Used to destroy slots in the reverse order:
+ // from last created to first created.
+ uint32_t sequence_num;
};
struct TlsVectorEntry {
- void* data;
+ // `data` is not a raw_ptr<...> for performance reasons (based on analysis of
+ // sampling profiler data and tab_search:top100:2020).
+ RAW_PTR_EXCLUSION void* data;
+
uint32_t version;
};
@@ -142,10 +177,78 @@
}
TlsMetadata g_tls_metadata[kThreadLocalStorageSize];
size_t g_last_assigned_slot = 0;
+uint32_t g_sequence_num = 0;
// The maximum number of times to try to clear slots by calling destructors.
// Use pthread naming convention for clarity.
-constexpr int kMaxDestructorIterations = kThreadLocalStorageSize;
+constexpr size_t kMaxDestructorIterations = kThreadLocalStorageSize;
+
+// Sets the value and state of the vector.
+void SetTlsVectorValue(PlatformThreadLocalStorage::TLSKey key,
+ TlsVectorEntry* tls_data,
+ TlsVectorState state) {
+ DCHECK(tls_data || (state == TlsVectorState::kUninitialized) ||
+ (state == TlsVectorState::kDestroyed));
+ PlatformThreadLocalStorage::SetTLSValue(
+ key, reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(tls_data) |
+ static_cast<uintptr_t>(state)));
+}
+
+// Returns the tls vector and current state from the raw tls value.
+TlsVectorState GetTlsVectorStateAndValue(void* tls_value,
+ TlsVectorEntry** entry = nullptr) {
+ if (entry) {
+ *entry = reinterpret_cast<TlsVectorEntry*>(
+ reinterpret_cast<uintptr_t>(tls_value) & ~kVectorStateBitMask);
+ }
+ return static_cast<TlsVectorState>(reinterpret_cast<uintptr_t>(tls_value) &
+ kVectorStateBitMask);
+}
+
+// Returns the tls vector and state using the tls key.
+TlsVectorState GetTlsVectorStateAndValue(PlatformThreadLocalStorage::TLSKey key,
+ TlsVectorEntry** entry = nullptr) {
+// Only on x86_64, the implementation is not stable on ARM64. For instance, in
+// macOS 11, the TPIDRRO_EL0 registers holds the CPU index in the low bits,
+// which is not the case in macOS 12. See libsyscall/os/tsd.h in XNU
+// (_os_tsd_get_direct() is used by pthread_getspecific() internally).
+#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)
+ // On macOS, pthread_getspecific() is in libSystem, so a call to it has to go
+ // through PLT. However, and contrary to some other platforms, *all* TLS keys
+ // are in a static array in the thread structure. So they are *always* at a
+ // fixed offset from the segment register holding the thread structure
+ // address.
+ //
+ // We could use _pthread_getspecific_direct(), but it is not
+ // exported. However, on all macOS versions we support, the TLS array is at
+ // %gs. This is used in V8 and PartitionAlloc, and can also be seen by looking
+ // at pthread_getspecific() disassembly:
+ //
+ // libsystem_pthread.dylib`pthread_getspecific:
+ // libsystem_pthread.dylib[0x7ff800316099] <+0>: movq %gs:(,%rdi,8), %rax
+ // libsystem_pthread.dylib[0x7ff8003160a2] <+9>: retq
+ //
+ // This function is essentially inlining the content of pthread_getspecific()
+ // here.
+ //
+ // Note that this likely ends up being even faster than thread_local for
+ // typical Chromium builds where the code is in a dynamic library. For the
+ // static executable case, this is likely equivalent.
+ static_assert(
+ std::is_same<PlatformThreadLocalStorage::TLSKey, pthread_key_t>::value,
+ "The special-case below assumes that the platform TLS implementation is "
+ "pthread.");
+
+ intptr_t platform_tls_value;
+ asm("movq %%gs:(,%1,8), %0;" : "=r"(platform_tls_value) : "r"(key));
+
+ return GetTlsVectorStateAndValue(reinterpret_cast<void*>(platform_tls_value),
+ entry);
+#else
+ return GetTlsVectorStateAndValue(PlatformThreadLocalStorage::GetTLSValue(key),
+ entry);
+#endif
+}
// This function is called to initialize our entire Chromium TLS system.
// It may be called very early, and we need to complete most all of the setup
@@ -155,8 +258,7 @@
// require memory allocations.
TlsVectorEntry* ConstructTlsVector() {
PlatformThreadLocalStorage::TLSKey key =
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key));
+ g_native_tls_key.load(std::memory_order_relaxed);
if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES) {
CHECK(PlatformThreadLocalStorage::AllocTLS(&key));
@@ -174,22 +276,19 @@
// Atomically test-and-set the tls_key. If the key is
// TLS_KEY_OUT_OF_INDEXES, go ahead and set it. Otherwise, do nothing, as
// another thread already did our dirty work.
- if (PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES !=
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_CompareAndSwap(
- &g_native_tls_key,
- reinterpret_cast<base::subtle::AtomicWord>(
- PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES),
- reinterpret_cast<base::subtle::AtomicWord>(key)))) {
+ PlatformThreadLocalStorage::TLSKey old_key =
+ PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES;
+ if (!g_native_tls_key.compare_exchange_strong(old_key, key,
+ std::memory_order_relaxed,
+ std::memory_order_relaxed)) {
// We've been shortcut. Another thread replaced g_native_tls_key first so
// we need to destroy our index and use the one the other thread got
// first.
PlatformThreadLocalStorage::FreeTLS(key);
- key = reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key));
+ key = g_native_tls_key.load(std::memory_order_relaxed);
}
}
- CHECK_EQ(PlatformThreadLocalStorage::GetTLSValue(key), kUninitialized);
+ CHECK_EQ(GetTlsVectorStateAndValue(key), TlsVectorState::kUninitialized);
// Some allocators, such as TCMalloc, make use of thread local storage. As a
// result, any attempt to call new (or malloc) will lazily cause such a system
@@ -202,28 +301,16 @@
TlsVectorEntry stack_allocated_tls_data[kThreadLocalStorageSize];
memset(stack_allocated_tls_data, 0, sizeof(stack_allocated_tls_data));
// Ensure that any rentrant calls change the temp version.
- PlatformThreadLocalStorage::SetTLSValue(key, stack_allocated_tls_data);
+ SetTlsVectorValue(key, stack_allocated_tls_data, TlsVectorState::kInUse);
// Allocate an array to store our data.
TlsVectorEntry* tls_data = new TlsVectorEntry[kThreadLocalStorageSize];
- memcpy(tls_data, stack_allocated_tls_data,
- sizeof(stack_allocated_tls_data));
- PlatformThreadLocalStorage::SetTLSValue(key, tls_data);
+ memcpy(tls_data, stack_allocated_tls_data, sizeof(stack_allocated_tls_data));
+ SetTlsVectorValue(key, tls_data, TlsVectorState::kInUse);
return tls_data;
}
void OnThreadExitInternal(TlsVectorEntry* tls_data) {
- // This branch is for POSIX, where this function is called twice. The first
- // pass calls dtors and sets state to kDestroyed. The second pass sets
- // kDestroyed to kUninitialized.
- if (tls_data == kDestroyed) {
- PlatformThreadLocalStorage::TLSKey key =
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key));
- PlatformThreadLocalStorage::SetTLSValue(key, kUninitialized);
- return;
- }
-
DCHECK(tls_data);
// Some allocators, such as TCMalloc, use TLS. As a result, when a thread
// terminates, one of the destructor calls we make may be to shut down an
@@ -235,33 +322,51 @@
// we have called all g_tls_metadata destructors. (i.e., don't even call
// delete[] after we're done with destructors.)
TlsVectorEntry stack_allocated_tls_data[kThreadLocalStorageSize];
- memcpy(stack_allocated_tls_data, tls_data,
- sizeof(stack_allocated_tls_data));
+ memcpy(stack_allocated_tls_data, tls_data, sizeof(stack_allocated_tls_data));
// Ensure that any re-entrant calls change the temp version.
PlatformThreadLocalStorage::TLSKey key =
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key));
- PlatformThreadLocalStorage::SetTLSValue(key, stack_allocated_tls_data);
+ g_native_tls_key.load(std::memory_order_relaxed);
+ SetTlsVectorValue(key, stack_allocated_tls_data, TlsVectorState::kDestroying);
delete[] tls_data; // Our last dependence on an allocator.
- // Snapshot the TLS Metadata so we don't have to lock on every access.
- TlsMetadata tls_metadata[kThreadLocalStorageSize];
- {
- base::AutoLock auto_lock(*GetTLSMetadataLock());
- memcpy(tls_metadata, g_tls_metadata, sizeof(g_tls_metadata));
- }
-
- int remaining_attempts = kMaxDestructorIterations;
+ size_t remaining_attempts = kMaxDestructorIterations + 1;
bool need_to_scan_destructors = true;
while (need_to_scan_destructors) {
need_to_scan_destructors = false;
- // Try to destroy the first-created-slot (which is slot 1) in our last
- // destructor call. That user was able to function, and define a slot with
- // no other services running, so perhaps it is a basic service (like an
- // allocator) and should also be destroyed last. If we get the order wrong,
- // then we'll iterate several more times, so it is really not that critical
- // (but it might help).
- for (int slot = 0; slot < kThreadLocalStorageSize ; ++slot) {
+
+ // Snapshot the TLS Metadata so we don't have to lock on every access.
+ TlsMetadata tls_metadata[kThreadLocalStorageSize];
+ {
+ base::AutoLock auto_lock(*GetTLSMetadataLock());
+ memcpy(tls_metadata, g_tls_metadata, sizeof(g_tls_metadata));
+ }
+
+ // We destroy slots in reverse order (i.e. destroy the first-created slot
+ // last), for the following reasons:
+ // 1) Slots that are created early belong to basic services (like an
+ // allocator) and might have to be recreated by destructors of other
+ // services. So we save iterations here by destroying them last.
+ // 2) Perfetto tracing service allocates a slot early and relies on it to
+ // keep emitting trace events while destructors of other slots are called,
+ // so it's important to keep it live to avoid use-after-free errors.
+ // To achieve this, we sort all slots in the order of decreasing sequence
+ // numbers.
+ struct OrderedSlot {
+ uint32_t sequence_num;
+ uint16_t slot;
+ } slot_destruction_order[kThreadLocalStorageSize];
+ for (uint16_t i = 0; i < kThreadLocalStorageSize; ++i) {
+ slot_destruction_order[i].sequence_num = tls_metadata[i].sequence_num;
+ slot_destruction_order[i].slot = i;
+ }
+ std::sort(std::begin(slot_destruction_order),
+ std::end(slot_destruction_order),
+ [](const OrderedSlot& s1, const OrderedSlot& s2) {
+ return s1.sequence_num > s2.sequence_num;
+ });
+
+ for (const auto& ordered_slot : slot_destruction_order) {
+ size_t slot = ordered_slot.slot;
void* tls_value = stack_allocated_tls_data[slot].data;
if (!tls_value || tls_metadata[slot].status == TlsStatus::FREE ||
stack_allocated_tls_data[slot].version != tls_metadata[slot].version)
@@ -278,14 +383,15 @@
// vector again. This is a pthread standard.
need_to_scan_destructors = true;
}
- if (--remaining_attempts <= 0) {
+
+ if (--remaining_attempts == 0) {
NOTREACHED(); // Destructors might not have been called.
break;
}
}
// Remove our stack allocated vector.
- PlatformThreadLocalStorage::SetTLSValue(key, kDestroyed);
+ SetTlsVectorValue(key, nullptr, TlsVectorState::kDestroyed);
}
} // namespace
@@ -294,58 +400,67 @@
namespace internal {
-#if defined(STARBOARD)
-void PlatformThreadLocalStorage::OnThreadExit(void* value) {
- OnThreadExitInternal(static_cast<TlsVectorEntry*>(value));
-}
-#else
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
void PlatformThreadLocalStorage::OnThreadExit() {
PlatformThreadLocalStorage::TLSKey key =
- base::subtle::NoBarrier_Load(&g_native_tls_key);
+ g_native_tls_key.load(std::memory_order_relaxed);
if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES)
return;
- void *tls_data = GetTLSValue(key);
+ TlsVectorEntry* tls_vector = nullptr;
+ const TlsVectorState state = GetTlsVectorStateAndValue(key, &tls_vector);
// On Windows, thread destruction callbacks are only invoked once per module,
// so there should be no way that this could be invoked twice.
- DCHECK_NE(tls_data, kDestroyed);
+ DCHECK_NE(state, TlsVectorState::kDestroyed);
// Maybe we have never initialized TLS for this thread.
- if (tls_data == kUninitialized)
+ if (state == TlsVectorState::kUninitialized)
return;
- OnThreadExitInternal(static_cast<TlsVectorEntry*>(tls_data));
+ OnThreadExitInternal(tls_vector);
}
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) || defined(STARBOARD)
void PlatformThreadLocalStorage::OnThreadExit(void* value) {
- OnThreadExitInternal(static_cast<TlsVectorEntry*>(value));
+ // On posix this function may be called twice. The first pass calls dtors and
+ // sets state to kDestroyed. The second pass sets kDestroyed to
+ // kUninitialized.
+ TlsVectorEntry* tls_vector = nullptr;
+ const TlsVectorState state = GetTlsVectorStateAndValue(value, &tls_vector);
+ if (state == TlsVectorState::kDestroyed) {
+ PlatformThreadLocalStorage::TLSKey key =
+ g_native_tls_key.load(std::memory_order_relaxed);
+ SetTlsVectorValue(key, nullptr, TlsVectorState::kUninitialized);
+ return;
+ }
+
+ OnThreadExitInternal(tls_vector);
}
-#endif // defined(OS_WIN)
-#endif
+#endif // BUILDFLAG(IS_WIN)
+
} // namespace internal
+// static
bool ThreadLocalStorage::HasBeenDestroyed() {
PlatformThreadLocalStorage::TLSKey key =
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key));
+ g_native_tls_key.load(std::memory_order_relaxed);
if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES)
return false;
- return PlatformThreadLocalStorage::GetTLSValue(key) == kDestroyed;
+ const TlsVectorState state = GetTlsVectorStateAndValue(key);
+ return state == TlsVectorState::kDestroying ||
+ state == TlsVectorState::kDestroyed;
}
void ThreadLocalStorage::Slot::Initialize(TLSDestructorFunc destructor) {
PlatformThreadLocalStorage::TLSKey key =
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key));
+ g_native_tls_key.load(std::memory_order_relaxed);
if (key == PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES ||
- PlatformThreadLocalStorage::GetTLSValue(key) == kUninitialized) {
+ GetTlsVectorStateAndValue(key) == TlsVectorState::kUninitialized) {
ConstructTlsVector();
}
// Grab a new slot.
{
base::AutoLock auto_lock(*GetTLSMetadataLock());
- for (int i = 0; i < kThreadLocalStorageSize; ++i) {
+ for (size_t i = 0; i < kThreadLocalStorageSize; ++i) {
// Tracking the last assigned slot is an attempt to find the next
// available slot within one iteration. Under normal usage, slots remain
// in use for the lifetime of the process (otherwise before we reclaimed
@@ -356,6 +471,7 @@
if (g_tls_metadata[slot_candidate].status == TlsStatus::FREE) {
g_tls_metadata[slot_candidate].status = TlsStatus::IN_USE;
g_tls_metadata[slot_candidate].destructor = destructor;
+ g_tls_metadata[slot_candidate].sequence_num = ++g_sequence_num;
g_last_assigned_slot = slot_candidate;
DCHECK_EQ(kInvalidSlotValue, slot_);
slot_ = slot_candidate;
@@ -364,12 +480,10 @@
}
}
}
- CHECK_NE(slot_, kInvalidSlotValue);
CHECK_LT(slot_, kThreadLocalStorageSize);
}
void ThreadLocalStorage::Slot::Free() {
- DCHECK_NE(slot_, kInvalidSlotValue);
DCHECK_LT(slot_, kThreadLocalStorageSize);
{
base::AutoLock auto_lock(*GetTLSMetadataLock());
@@ -381,14 +495,12 @@
}
void* ThreadLocalStorage::Slot::Get() const {
- TlsVectorEntry* tls_data = static_cast<TlsVectorEntry*>(
- PlatformThreadLocalStorage::GetTLSValue(
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key))));
- DCHECK_NE(tls_data, kDestroyed);
+ TlsVectorEntry* tls_data = nullptr;
+ const TlsVectorState state = GetTlsVectorStateAndValue(
+ g_native_tls_key.load(std::memory_order_relaxed), &tls_data);
+ DCHECK_NE(state, TlsVectorState::kDestroyed);
if (!tls_data)
return nullptr;
- DCHECK_NE(slot_, kInvalidSlotValue);
DCHECK_LT(slot_, kThreadLocalStorageSize);
// Version mismatches means this slot was previously freed.
if (tls_data[slot_].version != version_)
@@ -397,14 +509,15 @@
}
void ThreadLocalStorage::Slot::Set(void* value) {
- TlsVectorEntry* tls_data = static_cast<TlsVectorEntry*>(
- PlatformThreadLocalStorage::GetTLSValue(
- reinterpret_cast<PlatformThreadLocalStorage::TLSKey>(
- base::subtle::NoBarrier_Load(&g_native_tls_key))));
- DCHECK_NE(tls_data, kDestroyed);
- if (!tls_data)
+ TlsVectorEntry* tls_data = nullptr;
+ const TlsVectorState state = GetTlsVectorStateAndValue(
+ g_native_tls_key.load(std::memory_order_relaxed), &tls_data);
+ DCHECK_NE(state, TlsVectorState::kDestroyed);
+ if (UNLIKELY(!tls_data)) {
+ if (!value)
+ return;
tls_data = ConstructTlsVector();
- DCHECK_NE(slot_, kInvalidSlotValue);
+ }
DCHECK_LT(slot_, kThreadLocalStorageSize);
tls_data[slot_].data = value;
tls_data[slot_].version = version_;
diff --git a/base/threading/thread_local_storage.h b/base/threading/thread_local_storage.h
index 128ffd3..dfe8383 100644
--- a/base/threading/thread_local_storage.h
+++ b/base/threading/thread_local_storage.h
@@ -1,38 +1,27 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_THREAD_LOCAL_STORAGE_H_
#define BASE_THREADING_THREAD_LOCAL_STORAGE_H_
-#include "base/atomicops.h"
+#include <stdint.h>
+
#include "base/base_export.h"
-#include "base/macros.h"
#include "build/build_config.h"
#if defined(STARBOARD)
-#include "starboard/thread.h"
-#else
-#if defined(OS_WIN)
-#include "base/win/windows_types.h"
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
#include <pthread.h>
-
-#include "starboard/types.h"
+#elif BUILDFLAG(IS_WIN)
+#include "base/win/windows_types.h"
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+#include <pthread.h>
#endif
-#endif
-
-namespace heap_profiling {
-class ScopedAllowAlloc;
-class ScopedAllowRealloc;
-} // namespace heap_profiling
-
-namespace ui {
-class TLSDestructionCheckerForX11;
-}
namespace base {
+class SamplingHeapProfiler;
+
namespace debug {
class GlobalActivityTracker;
} // namespace debug
@@ -48,21 +37,14 @@
// WARNING: You should *NOT* use this class directly.
// PlatformThreadLocalStorage is a low-level abstraction of the OS's TLS
// interface. Instead, you should use one of the following:
-// * ThreadLocalBoolean (from thread_local.h) for booleans.
-// * ThreadLocalPointer (from thread_local.h) for pointers.
+// * ThreadLocalOwnedPointer (from thread_local.h) for unique_ptrs.
// * ThreadLocalStorage::StaticSlot/Slot for more direct control of the slot.
class BASE_EXPORT PlatformThreadLocalStorage {
public:
-
-#if defined(STARBOARD)
- typedef SbThreadLocalKey TLSKey;
- static constexpr SbThreadLocalKey TLS_KEY_OUT_OF_INDEXES =
- kSbThreadLocalKeyInvalid;
-#else
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN) && !defined(STARBOARD)
typedef unsigned long TLSKey;
enum : unsigned { TLS_KEY_OUT_OF_INDEXES = TLS_OUT_OF_INDEXES };
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) || defined(STARBOARD)
typedef pthread_key_t TLSKey;
// The following is a "reserved key" which is used in our generic Chromium
// ThreadLocalStorage implementation. We expect that an OS will not return
@@ -70,7 +52,6 @@
// will just request another key.
enum { TLS_KEY_OUT_OF_INDEXES = 0x7FFFFFFF };
#endif
-#endif
// The following methods need to be supported on each OS platform, so that
// the Chromium ThreadLocalStore functionality can be constructed.
@@ -86,14 +67,11 @@
static void FreeTLS(TLSKey key);
static void SetTLSValue(TLSKey key, void* value);
static void* GetTLSValue(TLSKey key) {
-#if defined(STARBOARD)
- return SbThreadGetLocalValue(key);
-#else
-#if defined(OS_WIN)
- return TlsGetValue(key);
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
return pthread_getspecific(key);
-#endif
+#if BUILDFLAG(IS_WIN) && !defined(STARBOARD)
+ return TlsGetValue(key);
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) || defined(STARBOARD)
+ return pthread_getspecific(key);
#endif
}
@@ -107,18 +85,16 @@
// destroyed.
#if defined(STARBOARD)
static void OnThreadExit(void* value);
-#else
-#if defined(OS_WIN)
+#elif BUILDFLAG(IS_WIN)
// Since Windows which doesn't support TLS destructor, the implementation
// should use GetTLSValue() to retrieve the value of TLS slot.
static void OnThreadExit();
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
// |Value| is the data stored in TLS slot, The implementation can't use
// GetTLSValue() to retrieve the value of slot as it has already been reset
// in Posix.
static void OnThreadExit(void* value);
#endif
-#endif
};
} // namespace internal
@@ -146,6 +122,10 @@
// |destructor| is a pointer to a function to perform per-thread cleanup of
// this object. If set to nullptr, no cleanup is done for this TLS slot.
explicit Slot(TLSDestructorFunc destructor = nullptr);
+
+ Slot(const Slot&) = delete;
+ Slot& operator=(const Slot&) = delete;
+
// If a destructor was set for this slot, removes the destructor so that
// remaining threads exiting will not free data.
~Slot();
@@ -162,31 +142,31 @@
void Initialize(TLSDestructorFunc destructor);
void Free();
- static constexpr int kInvalidSlotValue = -1;
- int slot_ = kInvalidSlotValue;
+ static constexpr size_t kInvalidSlotValue = static_cast<size_t>(-1);
+ size_t slot_ = kInvalidSlotValue;
uint32_t version_ = 0;
-
- DISALLOW_COPY_AND_ASSIGN(Slot);
};
+ ThreadLocalStorage(const ThreadLocalStorage&) = delete;
+ ThreadLocalStorage& operator=(const ThreadLocalStorage&) = delete;
+
private:
// In most cases, most callers should not need access to HasBeenDestroyed().
// If you are working in code that runs during thread destruction, contact the
// base OWNERs for advice and then make a friend request.
//
- // Returns |true| if Chrome's implementation of TLS has been destroyed during
- // thread destruction. Attempting to call Slot::Get() during destruction is
- // disallowed and will hit a DCHECK. Any code that relies on TLS during thread
- // destruction must first check this method before calling Slot::Get().
- friend class base::internal::ThreadLocalStorageTestInternal;
- friend class base::trace_event::MallocDumpProvider;
+ // Returns |true| if Chrome's implementation of TLS is being or has been
+ // destroyed during thread destruction. Attempting to call Slot::Get() during
+ // destruction is disallowed and will hit a DCHECK. Any code that relies on
+ // TLS during thread destruction must first check this method before calling
+ // Slot::Get().
+ friend class SequenceCheckerImpl;
+ friend class SamplingHeapProfiler;
+ friend class ThreadCheckerImpl;
+ friend class internal::ThreadLocalStorageTestInternal;
+ friend class trace_event::MallocDumpProvider;
friend class debug::GlobalActivityTracker;
- friend class heap_profiling::ScopedAllowAlloc;
- friend class heap_profiling::ScopedAllowRealloc;
- friend class ui::TLSDestructionCheckerForX11;
static bool HasBeenDestroyed();
-
- DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorage);
};
} // namespace base
diff --git a/base/threading/thread_local_storage_perftest.cc b/base/threading/thread_local_storage_perftest.cc
new file mode 100644
index 0000000..9f5d1c0
--- /dev/null
+++ b/base/threading/thread_local_storage_perftest.cc
@@ -0,0 +1,256 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stddef.h>
+#include <memory>
+#include <vector>
+
+#include "base/barrier_closure.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/bind.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread_local_storage.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/perf/perf_result_reporter.h"
+
+#if BUILDFLAG(IS_WIN)
+#include <windows.h>
+#include "base/win/windows_types.h"
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+#include <pthread.h>
+#endif
+
+namespace base {
+namespace internal {
+
+namespace {
+
+constexpr size_t kCount = 5000000;
+
+constexpr char kMetricPrefixThreadLocalStorage[] = "ThreadLocalStorage.";
+constexpr char kMetricBaseRead[] = "read";
+constexpr char kMetricBaseWrite[] = "write";
+constexpr char kMetricBaseReadWrite[] = "read_write";
+constexpr char kMetricSuffixThroughput[] = "_throughput";
+constexpr char kMetricSuffixOperationTime[] = "_operation_time";
+constexpr char kStoryBaseTLS[] = "thread_local_storage";
+#if BUILDFLAG(IS_WIN)
+constexpr char kStoryBasePlatformFLS[] = "platform_fiber_local_storage";
+#endif // BUILDFLAG(IS_WIN)
+constexpr char kStoryBasePlatformTLS[] = "platform_thread_local_storage";
+constexpr char kStoryBaseCPPTLS[] = "c++_platform_thread_local_storage";
+constexpr char kStorySuffixFourThreads[] = "_4_threads";
+
+perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixThreadLocalStorage,
+ story_name);
+ reporter.RegisterImportantMetric(
+ std::string(kMetricBaseRead) + kMetricSuffixThroughput, "runs/s");
+ reporter.RegisterImportantMetric(
+ std::string(kMetricBaseRead) + kMetricSuffixOperationTime, "ns");
+ reporter.RegisterImportantMetric(
+ std::string(kMetricBaseWrite) + kMetricSuffixThroughput, "runs/s");
+ reporter.RegisterImportantMetric(
+ std::string(kMetricBaseWrite) + kMetricSuffixOperationTime, "ns");
+ reporter.RegisterImportantMetric(
+ std::string(kMetricBaseReadWrite) + kMetricSuffixThroughput, "runs/s");
+ reporter.RegisterImportantMetric(
+ std::string(kMetricBaseReadWrite) + kMetricSuffixOperationTime, "ns");
+ return reporter;
+}
+
+// A thread that waits for the caller to signal an event before proceeding to
+// call action.Run().
+class TLSThread : public SimpleThread {
+ public:
+ // Creates a PostingThread that waits on |start_event| before calling
+ // action.Run().
+ TLSThread(WaitableEvent* start_event,
+ base::OnceClosure action,
+ base::OnceClosure completion)
+ : SimpleThread("TLSThread"),
+ start_event_(start_event),
+ action_(std::move(action)),
+ completion_(std::move(completion)) {
+ Start();
+ }
+
+ TLSThread(const TLSThread&) = delete;
+ TLSThread& operator=(const TLSThread&) = delete;
+
+ void Run() override {
+ start_event_->Wait();
+ std::move(action_).Run();
+ std::move(completion_).Run();
+ }
+
+ private:
+ const raw_ptr<WaitableEvent> start_event_;
+ base::OnceClosure action_;
+ base::OnceClosure completion_;
+};
+
+class ThreadLocalStoragePerfTest : public testing::Test {
+ public:
+ ThreadLocalStoragePerfTest(const ThreadLocalStoragePerfTest&) = delete;
+ ThreadLocalStoragePerfTest& operator=(const ThreadLocalStoragePerfTest&) =
+ delete;
+
+ protected:
+ ThreadLocalStoragePerfTest() = default;
+ ~ThreadLocalStoragePerfTest() override = default;
+
+ template <class Read, class Write>
+ void Benchmark(const std::string& story_name,
+ Read read,
+ Write write,
+ size_t num_operation,
+ size_t num_threads) {
+ write(2);
+
+ BenchmarkImpl(kMetricBaseRead, story_name,
+ base::BindLambdaForTesting([&]() {
+ volatile intptr_t total = 0;
+ for (size_t i = 0; i < num_operation; ++i)
+ total = total + read();
+ }),
+ num_operation, num_threads);
+
+ BenchmarkImpl(kMetricBaseWrite, story_name,
+ base::BindLambdaForTesting([&]() {
+ for (size_t i = 0; i < num_operation; ++i)
+ write(i);
+ }),
+ num_operation, num_threads);
+
+ BenchmarkImpl(kMetricBaseReadWrite, story_name,
+ base::BindLambdaForTesting([&]() {
+ for (size_t i = 0; i < num_operation; ++i)
+ write(read() + 1);
+ }),
+ num_operation, num_threads);
+ }
+
+ void BenchmarkImpl(const std::string& metric_base,
+ const std::string& story_name,
+ base::RepeatingClosure action,
+ size_t num_operation,
+ size_t num_threads) {
+ WaitableEvent start_thread;
+ WaitableEvent complete_thread;
+
+ base::RepeatingClosure done = BarrierClosure(
+ num_threads,
+ base::BindLambdaForTesting([&]() { complete_thread.Signal(); }));
+
+ std::vector<std::unique_ptr<TLSThread>> threads;
+ for (size_t i = 0; i < num_threads; ++i) {
+ threads.emplace_back(
+ std::make_unique<TLSThread>(&start_thread, action, done));
+ }
+
+ TimeTicks operation_start = TimeTicks::Now();
+ start_thread.Signal();
+ complete_thread.Wait();
+ TimeDelta operation_duration = TimeTicks::Now() - operation_start;
+
+ for (auto& thread : threads)
+ thread->Join();
+
+ auto reporter = SetUpReporter(story_name);
+ reporter.AddResult(metric_base + kMetricSuffixThroughput,
+ num_operation / operation_duration.InSecondsF());
+ size_t nanos_per_operation =
+ operation_duration.InNanoseconds() / num_operation;
+ reporter.AddResult(metric_base + kMetricSuffixOperationTime,
+ nanos_per_operation);
+ }
+};
+
+} // namespace
+
+TEST_F(ThreadLocalStoragePerfTest, ThreadLocalStorage) {
+ ThreadLocalStorage::Slot tls;
+ auto read = [&]() { return reinterpret_cast<intptr_t>(tls.Get()); };
+ auto write = [&](intptr_t value) { tls.Set(reinterpret_cast<void*>(value)); };
+
+ Benchmark(kStoryBaseTLS, read, write, 10000000, 1);
+ Benchmark(std::string(kStoryBaseTLS) + kStorySuffixFourThreads, read, write,
+ kCount, 4);
+}
+
+#if BUILDFLAG(IS_WIN)
+
+void WINAPI destroy(void*) {}
+
+TEST_F(ThreadLocalStoragePerfTest, PlatformFls) {
+ DWORD key = FlsAlloc(destroy);
+ ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
+
+ auto read = [&]() { return reinterpret_cast<intptr_t>(FlsGetValue(key)); };
+ auto write = [&](intptr_t value) {
+ FlsSetValue(key, reinterpret_cast<void*>(value));
+ };
+
+ Benchmark(kStoryBasePlatformFLS, read, write, 10000000, 1);
+ Benchmark(std::string(kStoryBasePlatformFLS) + kStorySuffixFourThreads, read,
+ write, kCount, 4);
+}
+
+TEST_F(ThreadLocalStoragePerfTest, PlatformTls) {
+ DWORD key = TlsAlloc();
+ ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
+
+ auto read = [&]() { return reinterpret_cast<intptr_t>(TlsGetValue(key)); };
+ auto write = [&](intptr_t value) {
+ TlsSetValue(key, reinterpret_cast<void*>(value));
+ };
+
+ Benchmark(kStoryBasePlatformTLS, read, write, 10000000, 1);
+ Benchmark(std::string(kStoryBasePlatformTLS) + kStorySuffixFourThreads, read,
+ write, kCount, 4);
+}
+
+#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
+
+TEST_F(ThreadLocalStoragePerfTest, PlatformTls) {
+ pthread_key_t key;
+ ASSERT_FALSE(pthread_key_create(&key, [](void*) {}));
+ ASSERT_NE(PlatformThreadLocalStorage::TLS_KEY_OUT_OF_INDEXES, key);
+
+ auto read = [&]() {
+ return reinterpret_cast<intptr_t>(pthread_getspecific(key));
+ };
+ auto write = [&](intptr_t value) {
+ pthread_setspecific(key, reinterpret_cast<void*>(value));
+ };
+
+ Benchmark(kStoryBasePlatformTLS, read, write, 10000000, 1);
+ Benchmark(std::string(kStoryBasePlatformTLS) + kStorySuffixFourThreads, read,
+ write, kCount, 4);
+}
+
+#endif
+
+TEST_F(ThreadLocalStoragePerfTest, Cpp11Tls) {
+ thread_local intptr_t thread_local_variable;
+
+ auto read = [&]() { return thread_local_variable; };
+ auto write = [&](intptr_t value) {
+ reinterpret_cast<volatile intptr_t*>(&thread_local_variable)[0] = value;
+ };
+
+ Benchmark(kStoryBaseCPPTLS, read, write, 10000000, 1);
+ Benchmark(std::string(kStoryBaseCPPTLS) + kStorySuffixFourThreads, read,
+ write, kCount, 4);
+}
+
+} // namespace internal
+} // namespace base
diff --git a/base/threading/thread_local_storage_posix.cc b/base/threading/thread_local_storage_posix.cc
index 89edeee..6469b10 100644
--- a/base/threading/thread_local_storage_posix.cc
+++ b/base/threading/thread_local_storage_posix.cc
@@ -1,10 +1,10 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread_local_storage.h"
-#include "base/logging.h"
+#include "base/check_op.h"
namespace base {
diff --git a/base/threading/thread_local_storage_starboard.cc b/base/threading/thread_local_storage_starboard.cc
index 063b8ad..e9e65b2 100644
--- a/base/threading/thread_local_storage_starboard.cc
+++ b/base/threading/thread_local_storage_starboard.cc
@@ -14,7 +14,7 @@
#include "base/threading/thread_local_storage.h"
-#include "base/logging.h"
+#include "base/notreached.h"
namespace base {
@@ -22,9 +22,8 @@
// static
bool PlatformThreadLocalStorage::AllocTLS(TLSKey* key) {
- *key = SbThreadCreateLocalKey(
- base::internal::PlatformThreadLocalStorage::OnThreadExit);
- if (!SbThreadIsValidLocalKey(*key)) {
+ int res = pthread_key_create(key, base::internal::PlatformThreadLocalStorage::OnThreadExit);
+ if (res != 0) {
NOTREACHED();
return false;
}
@@ -34,12 +33,12 @@
// static
void PlatformThreadLocalStorage::FreeTLS(TLSKey key) {
- SbThreadDestroyLocalKey(key);
+ pthread_key_delete(key);
}
// static
void PlatformThreadLocalStorage::SetTLSValue(TLSKey key, void* value) {
- if (!SbThreadSetLocalValue(key, value)) {
+ if (pthread_setspecific(key, value) != 0) {
NOTREACHED();
}
}
diff --git a/base/threading/thread_local_storage_unittest.cc b/base/threading/thread_local_storage_unittest.cc
index e0b9794..b01ba4b 100644
--- a/base/threading/thread_local_storage_unittest.cc
+++ b/base/threading/thread_local_storage_unittest.cc
@@ -1,22 +1,19 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread_local_storage.h"
-#if defined(OS_WIN)
-#include <windows.h>
-#include <process.h>
-#endif
-
-#include "base/macros.h"
+#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/threading/simple_thread.h"
#include "build/build_config.h"
-#include "starboard/types.h"
#include "testing/gtest/include/gtest/gtest.h"
-#if defined(OS_WIN)
+#if BUILDFLAG(IS_WIN)
+#include <windows.h>
+
+#include <process.h>
// Ignore warnings about ptr->int conversions that we use when
// storing ints into ThreadLocalStorage.
#pragma warning(disable : 4311 4312)
@@ -24,7 +21,7 @@
namespace base {
-#if defined(OS_POSIX)
+#if BUILDFLAG(IS_POSIX)
namespace internal {
@@ -38,7 +35,7 @@
} // namespace internal
-#endif // defined(OS_POSIX)
+#endif // BUILDFLAG(IS_POSIX)
namespace {
@@ -60,6 +57,9 @@
explicit ThreadLocalStorageRunner(int* tls_value_ptr)
: tls_value_ptr_(tls_value_ptr) {}
+ ThreadLocalStorageRunner(const ThreadLocalStorageRunner&) = delete;
+ ThreadLocalStorageRunner& operator=(const ThreadLocalStorageRunner&) = delete;
+
~ThreadLocalStorageRunner() override = default;
void Run() override {
@@ -79,15 +79,14 @@
}
private:
- int* tls_value_ptr_;
- DISALLOW_COPY_AND_ASSIGN(ThreadLocalStorageRunner);
+ raw_ptr<int> tls_value_ptr_;
};
void ThreadLocalStorageCleanup(void *value) {
- int *ptr = reinterpret_cast<int*>(value);
+ int *ptr = static_cast<int*>(value);
// Destructors should never be called with a NULL.
- ASSERT_NE(reinterpret_cast<int*>(NULL), ptr);
+ ASSERT_NE(nullptr, ptr);
if (*ptr == kFinalTlsValue)
return; // We've been called enough times.
ASSERT_LT(kFinalTlsValue, *ptr);
@@ -97,7 +96,7 @@
TLSSlot().Set(value);
}
-#if defined(OS_POSIX)
+#if BUILDFLAG(IS_POSIX) && !defined(STARBOARD)
constexpr intptr_t kDummyValue = 0xABCD;
constexpr size_t kKeyCount = 20;
@@ -108,6 +107,10 @@
public:
UseTLSDuringDestructionRunner() = default;
+ UseTLSDuringDestructionRunner(const UseTLSDuringDestructionRunner&) = delete;
+ UseTLSDuringDestructionRunner& operator=(
+ const UseTLSDuringDestructionRunner&) = delete;
+
// The order in which pthread_key destructors are called is not well defined.
// Hopefully, by creating 10 both before and after initializing TLS on the
// thread, at least 1 will be called after TLS destruction.
@@ -135,7 +138,7 @@
private:
struct TLSState {
pthread_key_t key;
- bool* teardown_works_correctly;
+ raw_ptr<bool> teardown_works_correctly;
};
// The POSIX TLS destruction API takes as input a single C-function, which is
@@ -181,8 +184,6 @@
static base::ThreadLocalStorage::Slot slot_;
bool teardown_works_correctly_ = false;
TLSState tls_states_[kKeyCount];
-
- DISALLOW_COPY_AND_ASSIGN(UseTLSDuringDestructionRunner);
};
base::ThreadLocalStorage::Slot UseTLSDuringDestructionRunner::slot_;
@@ -194,7 +195,70 @@
return nullptr;
}
-#endif // defined(OS_POSIX)
+#endif // BUILDFLAG(IS_POSIX)
+
+class TlsDestructionOrderRunner : public DelegateSimpleThread::Delegate {
+ public:
+ // The runner creates |n_slots| static slots that will be destroyed at
+ // thread exit, with |spacing| empty slots between them. This allows us to
+ // test that the destruction order is correct regardless of the actual slot
+ // indices in the global array.
+ TlsDestructionOrderRunner(int n_slots, int spacing)
+ : n_slots_(n_slots), spacing_(spacing) {}
+
+ void Run() override {
+ destructor_calls.clear();
+ for (int slot = 1; slot < n_slots_ + 1; ++slot) {
+ for (int i = 0; i < spacing_; ++i) {
+ ThreadLocalStorage::Slot empty_slot(nullptr);
+ }
+ NewStaticTLSSlot(slot);
+ }
+ }
+
+ static std::vector<int> destructor_calls;
+
+ private:
+ ThreadLocalStorage::Slot& NewStaticTLSSlot(int n) {
+ NoDestructor<ThreadLocalStorage::Slot> slot(
+ &TlsDestructionOrderRunner::Destructor);
+ slot->Set(reinterpret_cast<void*>(n));
+ return *slot;
+ }
+
+ static void Destructor(void* value) {
+ int n = reinterpret_cast<intptr_t>(value);
+ destructor_calls.push_back(n);
+ }
+
+ int n_slots_;
+ int spacing_;
+};
+std::vector<int> TlsDestructionOrderRunner::destructor_calls;
+
+class CreateDuringDestructionRunner : public DelegateSimpleThread::Delegate {
+ public:
+ void Run() override {
+ second_destructor_called = false;
+ NoDestructor<ThreadLocalStorage::Slot> slot(
+ &CreateDuringDestructionRunner::FirstDestructor);
+ slot->Set(reinterpret_cast<void*>(123));
+ }
+
+ static bool second_destructor_called;
+
+ private:
+ // The first destructor allocates another TLS slot, which should also be
+ // destroyed eventually.
+ static void FirstDestructor(void*) {
+ NoDestructor<ThreadLocalStorage::Slot> slot(
+ &CreateDuringDestructionRunner::SecondDestructor);
+ slot->Set(reinterpret_cast<void*>(234));
+ }
+
+ static void SecondDestructor(void*) { second_destructor_called = true; }
+};
+bool CreateDuringDestructionRunner::second_destructor_called = false;
} // namespace
@@ -205,16 +269,12 @@
EXPECT_EQ(value, 123);
}
-#if defined(THREAD_SANITIZER) || \
- (defined(OS_WIN) && defined(ARCH_CPU_X86_64) && !defined(NDEBUG))
+#if defined(THREAD_SANITIZER)
// Do not run the test under ThreadSanitizer. Because this test iterates its
// own TSD destructor for the maximum possible number of times, TSan can't jump
// in after the last destructor invocation, therefore the destructor remains
// unsynchronized with the following users of the same TSD slot. This results
// in race reports between the destructor and functions in other tests.
-//
-// It is disabled on Win x64 with incremental linking (i.e. "Debug") pending
-// resolution of http://crbug.com/251251.
#define MAYBE_TLSDestructors DISABLED_TLSDestructors
#else
#define MAYBE_TLSDestructors TLSDestructors
@@ -248,8 +308,6 @@
}
}
-// MSVC doesn't allow casting 32bit address to pointer.
-#if !SB_IS(COMPILER_MSVC)
TEST(ThreadLocalStorageTest, TLSReclaim) {
// Creates and destroys many TLS slots and ensures they all zero-inited.
for (int i = 0; i < 1000; ++i) {
@@ -259,9 +317,8 @@
EXPECT_EQ(reinterpret_cast<void*>(0xBAADF00D), slot.Get());
}
}
-#endif
-#if defined(OS_POSIX)
+#if BUILDFLAG(IS_POSIX) && !defined(STARBOARD)
// Unlike POSIX, Windows does not iterate through the OS TLS to cleanup any
// values there. Instead a per-module thread destruction function is called.
// However, it is not possible to perform a check after this point (as the code
@@ -277,6 +334,33 @@
EXPECT_TRUE(runner.teardown_works_correctly());
}
-#endif // defined(OS_POSIX)
+#endif // BUILDFLAG(IS_POSIX)
+
+// Test that TLS slots are destroyed in the reverse order: the one that was
+// created first is destroyed last.
+TEST(ThreadLocalStorageTest, DestructionOrder) {
+ const size_t kNSlots = 5;
+ const size_t kSpacing = 100;
+ // The total number of slots is 256, so creating 5 slots with 100 space
+ // between them will place them in different parts of the slot array.
+ // This test checks that their destruction order depends only on their
+ // creation order and not on their index in the array.
+ TlsDestructionOrderRunner runner(kNSlots, kSpacing);
+ DelegateSimpleThread thread(&runner, "tls thread");
+ thread.Start();
+ thread.Join();
+ ASSERT_EQ(kNSlots, TlsDestructionOrderRunner::destructor_calls.size());
+ for (int call = 0, slot = kNSlots; slot > 0; --slot, ++call) {
+ EXPECT_EQ(slot, TlsDestructionOrderRunner::destructor_calls[call]);
+ }
+}
+
+TEST(ThreadLocalStorageTest, CreateDuringDestruction) {
+ CreateDuringDestructionRunner runner;
+ DelegateSimpleThread thread(&runner, "tls thread");
+ thread.Start();
+ thread.Join();
+ ASSERT_TRUE(CreateDuringDestructionRunner::second_destructor_called);
+}
} // namespace base
diff --git a/base/threading/thread_local_storage_win.cc b/base/threading/thread_local_storage_win.cc
index 4907624..f88f27f 100644
--- a/base/threading/thread_local_storage_win.cc
+++ b/base/threading/thread_local_storage_win.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,8 +6,7 @@
#include <windows.h>
-#include "base/logging.h"
-#include "starboard/types.h"
+#include "base/check.h"
namespace base {
diff --git a/base/threading/thread_local_unittest.cc b/base/threading/thread_local_unittest.cc
index 54f2ad2..e38a32a 100644
--- a/base/threading/thread_local_unittest.cc
+++ b/base/threading/thread_local_unittest.cc
@@ -1,164 +1,206 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "base/logging.h"
-#include "base/threading/simple_thread.h"
#include "base/threading/thread_local.h"
+#include "base/check_op.h"
+#include "base/memory/raw_ptr.h"
#include "base/synchronization/waitable_event.h"
+#include "base/test/bind.h"
+#include "base/test/gtest_util.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
namespace base {
namespace {
-class ThreadLocalTesterBase : public base::DelegateSimpleThreadPool::Delegate {
+// A simple helper which sets the given boolean to true on destruction.
+class SetTrueOnDestruction {
public:
- typedef base::ThreadLocalPointer<char> TLPType;
-
- ThreadLocalTesterBase(TLPType* tlp, base::WaitableEvent* done)
- : tlp_(tlp),
- done_(done) {
+ explicit SetTrueOnDestruction(bool* was_destroyed)
+ : was_destroyed_(was_destroyed) {
+ CHECK_NE(was_destroyed, nullptr);
}
- ~ThreadLocalTesterBase() override = default;
- protected:
- TLPType* tlp_;
- base::WaitableEvent* done_;
-};
+ SetTrueOnDestruction(const SetTrueOnDestruction&) = delete;
+ SetTrueOnDestruction& operator=(const SetTrueOnDestruction&) = delete;
-class SetThreadLocal : public ThreadLocalTesterBase {
- public:
- SetThreadLocal(TLPType* tlp, base::WaitableEvent* done)
- : ThreadLocalTesterBase(tlp, done), val_(nullptr) {}
- ~SetThreadLocal() override = default;
-
- void set_value(char* val) { val_ = val; }
-
- void Run() override {
- DCHECK(!done_->IsSignaled());
- tlp_->Set(val_);
- done_->Signal();
+ ~SetTrueOnDestruction() {
+ EXPECT_FALSE(*was_destroyed_);
+ *was_destroyed_ = true;
}
private:
- char* val_;
-};
-
-class GetThreadLocal : public ThreadLocalTesterBase {
- public:
- GetThreadLocal(TLPType* tlp, base::WaitableEvent* done)
- : ThreadLocalTesterBase(tlp, done), ptr_(nullptr) {}
- ~GetThreadLocal() override = default;
-
- void set_ptr(char** ptr) { ptr_ = ptr; }
-
- void Run() override {
- DCHECK(!done_->IsSignaled());
- *ptr_ = tlp_->Get();
- done_->Signal();
- }
-
- private:
- char** ptr_;
+ const raw_ptr<bool> was_destroyed_;
};
} // namespace
-// In this test, we start 2 threads which will access a ThreadLocalPointer. We
-// make sure the default is NULL, and the pointers are unique to the threads.
-TEST(ThreadLocalTest, Pointer) {
- base::DelegateSimpleThreadPool tp1("ThreadLocalTest tp1", 1);
- base::DelegateSimpleThreadPool tp2("ThreadLocalTest tp1", 1);
- tp1.Start();
- tp2.Start();
+TEST(ThreadLocalTest, ThreadLocalOwnedPointerBasic) {
+ ThreadLocalOwnedPointer<SetTrueOnDestruction> tls_owned_pointer;
+ EXPECT_FALSE(tls_owned_pointer.Get());
- base::ThreadLocalPointer<char> tlp;
+ bool was_destroyed1 = false;
+ tls_owned_pointer.Set(
+ std::make_unique<SetTrueOnDestruction>(&was_destroyed1));
+ EXPECT_FALSE(was_destroyed1);
+ EXPECT_TRUE(tls_owned_pointer.Get());
- static char* const kBogusPointer = reinterpret_cast<char*>(0x1234);
+ bool was_destroyed2 = false;
+ tls_owned_pointer.Set(
+ std::make_unique<SetTrueOnDestruction>(&was_destroyed2));
+ EXPECT_TRUE(was_destroyed1);
+ EXPECT_FALSE(was_destroyed2);
+ EXPECT_TRUE(tls_owned_pointer.Get());
- char* tls_val;
- base::WaitableEvent done(WaitableEvent::ResetPolicy::MANUAL,
- WaitableEvent::InitialState::NOT_SIGNALED);
-
- GetThreadLocal getter(&tlp, &done);
- getter.set_ptr(&tls_val);
-
- // Check that both threads defaulted to NULL.
- tls_val = kBogusPointer;
- done.Reset();
- tp1.AddWork(&getter);
- done.Wait();
- EXPECT_EQ(static_cast<char*>(nullptr), tls_val);
-
- tls_val = kBogusPointer;
- done.Reset();
- tp2.AddWork(&getter);
- done.Wait();
- EXPECT_EQ(static_cast<char*>(nullptr), tls_val);
-
- SetThreadLocal setter(&tlp, &done);
- setter.set_value(kBogusPointer);
-
- // Have thread 1 set their pointer value to kBogusPointer.
- done.Reset();
- tp1.AddWork(&setter);
- done.Wait();
-
- tls_val = nullptr;
- done.Reset();
- tp1.AddWork(&getter);
- done.Wait();
- EXPECT_EQ(kBogusPointer, tls_val);
-
- // Make sure thread 2 is still NULL
- tls_val = kBogusPointer;
- done.Reset();
- tp2.AddWork(&getter);
- done.Wait();
- EXPECT_EQ(static_cast<char*>(nullptr), tls_val);
-
- // Set thread 2 to kBogusPointer + 1.
- setter.set_value(kBogusPointer + 1);
-
- done.Reset();
- tp2.AddWork(&setter);
- done.Wait();
-
- tls_val = nullptr;
- done.Reset();
- tp2.AddWork(&getter);
- done.Wait();
- EXPECT_EQ(kBogusPointer + 1, tls_val);
-
- // Make sure thread 1 is still kBogusPointer.
- tls_val = nullptr;
- done.Reset();
- tp1.AddWork(&getter);
- done.Wait();
- EXPECT_EQ(kBogusPointer, tls_val);
-
- tp1.JoinAll();
- tp2.JoinAll();
+ tls_owned_pointer.Set(nullptr);
+ EXPECT_TRUE(was_destroyed1);
+ EXPECT_TRUE(was_destroyed2);
+ EXPECT_FALSE(tls_owned_pointer.Get());
}
-TEST(ThreadLocalTest, Boolean) {
- {
- base::ThreadLocalBoolean tlb;
- EXPECT_FALSE(tlb.Get());
+TEST(ThreadLocalTest, ThreadLocalOwnedPointerFreedOnThreadExit) {
+ bool tls_was_destroyed = false;
+ ThreadLocalOwnedPointer<SetTrueOnDestruction> tls_owned_pointer;
- tlb.Set(false);
- EXPECT_FALSE(tlb.Get());
+ Thread thread("TestThread");
+ thread.Start();
- tlb.Set(true);
- EXPECT_TRUE(tlb.Get());
+ WaitableEvent tls_set;
+
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindLambdaForTesting([&]() {
+ tls_owned_pointer.Set(
+ std::make_unique<SetTrueOnDestruction>(&tls_was_destroyed));
+ tls_set.Signal();
+ }));
+
+ tls_set.Wait();
+ EXPECT_FALSE(tls_was_destroyed);
+
+ thread.Stop();
+ EXPECT_TRUE(tls_was_destroyed);
+}
+
+TEST(ThreadLocalTest, ThreadLocalOwnedPointerCleansUpMainThreadOnDestruction) {
+ absl::optional<ThreadLocalOwnedPointer<SetTrueOnDestruction>>
+ tls_owned_pointer(absl::in_place);
+ bool tls_was_destroyed_other = false;
+
+ Thread thread("TestThread");
+ thread.Start();
+
+ WaitableEvent tls_set;
+
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindLambdaForTesting([&]() {
+ tls_owned_pointer->Set(
+ std::make_unique<SetTrueOnDestruction>(&tls_was_destroyed_other));
+ tls_set.Signal();
+ }));
+
+ tls_set.Wait();
+
+ bool tls_was_destroyed_main = false;
+ tls_owned_pointer->Set(
+ std::make_unique<SetTrueOnDestruction>(&tls_was_destroyed_main));
+ EXPECT_FALSE(tls_was_destroyed_other);
+ EXPECT_FALSE(tls_was_destroyed_main);
+
+ // Stopping the thread relinquishes its TLS (as in
+ // ThreadLocalOwnedPointerFreedOnThreadExit).
+ thread.Stop();
+ EXPECT_TRUE(tls_was_destroyed_other);
+ EXPECT_FALSE(tls_was_destroyed_main);
+
+ // Deleting the ThreadLocalOwnedPointer instance on the main thread is allowed
+ // iff that's the only thread with remaining storage (ref. disallowed use case
+ // in ThreadLocalOwnedPointerDeathIfDestroyedWithActiveThread below). In that
+ // case, the storage on the main thread is freed before releasing the TLS
+ // slot.
+ tls_owned_pointer.reset();
+ EXPECT_TRUE(tls_was_destroyed_main);
+}
+
+TEST(ThreadLocalTest, ThreadLocalOwnedPointerDeathIfDestroyedWithActiveThread) {
+ testing::FLAGS_gtest_death_test_style = "threadsafe";
+
+ absl::optional<ThreadLocalOwnedPointer<int>> tls_owned_pointer(
+ absl::in_place);
+
+ Thread thread("TestThread");
+ thread.Start();
+
+ WaitableEvent tls_set;
+
+ thread.task_runner()->PostTask(
+ FROM_HERE, BindLambdaForTesting([&]() {
+ tls_owned_pointer->Set(std::make_unique<int>(1));
+ tls_set.Signal();
+ }));
+
+ tls_set.Wait();
+
+ EXPECT_DCHECK_DEATH({ tls_owned_pointer.reset(); });
+}
+
+TEST(ThreadLocalTest, ThreadLocalOwnedPointerMultiThreadedAndStaticStorage) {
+ constexpr int kNumThreads = 16;
+
+ static ThreadLocalOwnedPointer<SetTrueOnDestruction> tls_owned_pointer;
+
+ std::array<bool, kNumThreads> were_destroyed{};
+
+ std::array<std::unique_ptr<Thread>, kNumThreads> threads;
+
+ for (auto& thread : threads) {
+ thread = std::make_unique<Thread>("TestThread");
+ thread->Start();
}
- // Our slot should have been freed, we're all reset.
- {
- base::ThreadLocalBoolean tlb;
- EXPECT_FALSE(tlb.Get());
+ for (const auto& thread : threads) {
+ // Waiting is unnecessary but enhances the likelihood of data races in the
+ // next steps.
+ thread->WaitUntilThreadStarted();
}
+
+ for (const bool was_destroyed : were_destroyed) {
+ EXPECT_FALSE(was_destroyed);
+ }
+
+ for (int i = 0; i < kNumThreads; ++i) {
+ threads[i]->task_runner()->PostTask(
+ FROM_HERE,
+ BindOnce(
+ [](bool* was_destroyed) {
+ tls_owned_pointer.Set(
+ std::make_unique<SetTrueOnDestruction>(was_destroyed));
+ },
+ &were_destroyed[i]));
+ }
+
+ static bool main_thread_was_destroyed = false;
+ // Even when the test is run multiple times in the same process: TLS should
+ // never be destroyed until static uninitialization.
+ EXPECT_FALSE(main_thread_was_destroyed);
+
+ tls_owned_pointer.Set(
+ std::make_unique<SetTrueOnDestruction>(&main_thread_was_destroyed));
+
+ for (const auto& thread : threads) {
+ thread->Stop();
+ }
+
+ for (const bool was_destroyed : were_destroyed) {
+ EXPECT_TRUE(was_destroyed);
+ }
+
+ // The main thread's TLS still wasn't destroyed (let the test unfold naturally
+ // through static uninitialization).
+ EXPECT_FALSE(main_thread_was_destroyed);
}
} // namespace base
diff --git a/base/threading/thread_perftest.cc b/base/threading/thread_perftest.cc
index 9a3f92c..3c623f1 100644
--- a/base/threading/thread_perftest.cc
+++ b/base/threading/thread_perftest.cc
@@ -1,30 +1,30 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <stddef.h>
+
#include <memory>
#include <vector>
-#include "starboard/types.h"
-
#include "base/base_switches.h"
-#include "base/bind.h"
#include "base/command_line.h"
+#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
-#include "base/single_thread_task_runner.h"
-#include "base/strings/stringprintf.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
+#include "base/task/current_thread.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/task/task_observer.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
-#if defined(OS_POSIX)
+#if BUILDFLAG(IS_POSIX)
#include <pthread.h>
#endif
@@ -34,6 +34,27 @@
const int kNumRuns = 100000;
+constexpr char kMetricPrefixThread[] = "Thread.";
+constexpr char kMetricClockTimePerHop[] = "wall_time_per_hop";
+constexpr char kMetricCpuTimePerHop[] = "cpu_time_per_hop";
+constexpr char kStoryBaseTask[] = "task";
+constexpr char kStoryBaseTaskWithObserver[] = "task_with_observer";
+constexpr char kStoryBaseWaitableEvent[] = "waitable_event";
+constexpr char kStoryBaseCondVar[] = "condition_variable";
+constexpr char kStorySuffixOneThread[] = "_1_thread";
+constexpr char kStorySuffixFourThreads[] = "_4_threads";
+
+#if BUILDFLAG(IS_POSIX)
+constexpr char kStoryBasePthreadCondVar[] = "pthread_condition_variable";
+#endif // BUILDFLAG(IS_POSIX)
+
+perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+ perf_test::PerfResultReporter reporter(kMetricPrefixThread, story_name);
+ reporter.RegisterImportantMetric(kMetricClockTimePerHop, "us");
+ reporter.RegisterImportantMetric(kMetricCpuTimePerHop, "us");
+ return reporter;
+}
+
// Base class for a threading perf-test. This sets up some threads for the
// test and measures the clock-time in addition to time spent on each thread.
class ThreadPerfTest : public testing::Test {
@@ -68,7 +89,7 @@
return ticks;
}
- void RunPingPongTest(const std::string& name, unsigned num_threads) {
+ void RunPingPongTest(const std::string& story_name, unsigned num_threads) {
// Create threads and collect starting cpu-time for each thread.
std::vector<base::ThreadTicks> thread_starts;
while (threads_.size() < num_threads) {
@@ -98,18 +119,16 @@
Reset();
- double num_runs = static_cast<double>(kNumRuns);
- double us_per_task_clock = (end - start).InMicroseconds() / num_runs;
- double us_per_task_cpu = thread_time.InMicroseconds() / num_runs;
+ double us_per_task_clock = (end - start).InMicrosecondsF() / kNumRuns;
+ double us_per_task_cpu = thread_time.InMicrosecondsF() / kNumRuns;
+ auto reporter = SetUpReporter(story_name);
// Clock time per task.
- perf_test::PrintResult(
- "task", "", name + "_time ", us_per_task_clock, "us/hop", true);
+ reporter.AddResult(kMetricClockTimePerHop, us_per_task_clock);
// Total utilization across threads if available (likely higher).
if (base::ThreadTicks::IsSupported()) {
- perf_test::PrintResult(
- "task", "", name + "_cpu ", us_per_task_cpu, "us/hop", true);
+ reporter.AddResult(kMetricCpuTimePerHop, us_per_task_cpu);
}
}
@@ -144,15 +163,16 @@
// used to ensure the threads do yeild (with just two it might be possible for
// both threads to stay awake if they can signal each other fast enough).
TEST_F(TaskPerfTest, TaskPingPong) {
- RunPingPongTest("1_Task_Threads", 1);
- RunPingPongTest("4_Task_Threads", 4);
+ RunPingPongTest(std::string(kStoryBaseTask) + kStorySuffixOneThread, 1);
+ RunPingPongTest(std::string(kStoryBaseTask) + kStorySuffixFourThreads, 4);
}
// Same as above, but add observers to test their perf impact.
-class MessageLoopObserver : public base::MessageLoop::TaskObserver {
+class MessageLoopObserver : public base::TaskObserver {
public:
- void WillProcessTask(const base::PendingTask& pending_task) override {}
+ void WillProcessTask(const base::PendingTask& pending_task,
+ bool was_blocked_or_low_priority) override {}
void DidProcessTask(const base::PendingTask& pending_task) override {}
};
MessageLoopObserver message_loop_observer;
@@ -161,18 +181,22 @@
public:
void Init() override {
TaskPerfTest::Init();
- for (size_t i = 0; i < threads_.size(); i++) {
- threads_[i]->message_loop()->task_runner()->PostTask(
- FROM_HERE, BindOnce(&MessageLoop::AddTaskObserver,
- Unretained(threads_[i]->message_loop()),
- Unretained(&message_loop_observer)));
+ for (auto& i : threads_) {
+ i->task_runner()->PostTask(
+ FROM_HERE, BindOnce(
+ [](MessageLoopObserver* observer) {
+ CurrentThread::Get()->AddTaskObserver(observer);
+ },
+ Unretained(&message_loop_observer)));
}
}
};
TEST_F(TaskObserverPerfTest, TaskPingPong) {
- RunPingPongTest("1_Task_Threads_With_Observer", 1);
- RunPingPongTest("4_Task_Threads_With_Observer", 4);
+ RunPingPongTest(
+ std::string(kStoryBaseTaskWithObserver) + kStorySuffixOneThread, 1);
+ RunPingPongTest(
+ std::string(kStoryBaseTaskWithObserver) + kStorySuffixFourThreads, 4);
}
// Class to test our WaitableEvent performance by signaling back and fort.
@@ -226,7 +250,8 @@
// end up blocking because the event is already signalled).
typedef EventPerfTest<base::WaitableEvent> WaitableEventThreadPerfTest;
TEST_F(WaitableEventThreadPerfTest, EventPingPong) {
- RunPingPongTest("4_WaitableEvent_Threads", 4);
+ RunPingPongTest(
+ std::string(kStoryBaseWaitableEvent) + kStorySuffixFourThreads, 4);
}
// Build a minimal event using ConditionVariable.
@@ -264,9 +289,9 @@
// using our own base synchronization code.
typedef EventPerfTest<ConditionVariableEvent> ConditionVariablePerfTest;
TEST_F(ConditionVariablePerfTest, EventPingPong) {
- RunPingPongTest("4_ConditionVariable_Threads", 4);
+ RunPingPongTest(std::string(kStoryBaseCondVar) + kStorySuffixFourThreads, 4);
}
-#if defined(OS_POSIX)
+#if BUILDFLAG(IS_POSIX)
// Absolutely 100% minimal posix waitable event. If there is a better/faster
// way to force a context switch, we should use that instead.
@@ -311,7 +336,8 @@
// If there is any faster way to do this we should substitute it in.
typedef EventPerfTest<PthreadEvent> PthreadEventPerfTest;
TEST_F(PthreadEventPerfTest, EventPingPong) {
- RunPingPongTest("4_PthreadCondVar_Threads", 4);
+ RunPingPongTest(
+ std::string(kStoryBasePthreadCondVar) + kStorySuffixFourThreads, 4);
}
#endif
diff --git a/base/threading/thread_restrictions.cc b/base/threading/thread_restrictions.cc
index 3d91fe0..016841c 100644
--- a/base/threading/thread_restrictions.cc
+++ b/base/threading/thread_restrictions.cc
@@ -1,199 +1,330 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread_restrictions.h"
-#if DCHECK_IS_ON()
+#include "base/threading/hang_watcher.h"
+#include "base/trace_event/base_tracing.h"
+#include "build/build_config.h"
-#include "base/lazy_instance.h"
-#include "base/logging.h"
+#if DCHECK_IS_ON()
+#include "base/check_op.h"
+#include "base/no_destructor.h"
#include "base/threading/thread_local.h"
+// NaCL doesn't support stack sampling and Android is slow at stack sampling and
+// this causes timeouts (crbug.com/959139).
+#if BUILDFLAG(IS_NACL) || BUILDFLAG(IS_ANDROID)
+constexpr bool kCaptureStackTraces = false;
+#else
+// Always disabled when !EXPENSIVE_DCHECKS_ARE_ON() because user-facing builds
+// typically drop log strings anyways.
+constexpr bool kCaptureStackTraces = EXPENSIVE_DCHECKS_ARE_ON();
+#endif
+
namespace base {
+BooleanWithStack::BooleanWithStack(bool value) : value_(value) {
+ if (kCaptureStackTraces) {
+ stack_.emplace();
+ }
+}
+
+std::ostream& operator<<(std::ostream& out, const BooleanWithStack& bws) {
+ out << bws.value_;
+ if (kCaptureStackTraces) {
+ if (bws.stack_.has_value()) {
+ out << " set by\n" << bws.stack_.value();
+ } else {
+ out << " (value by default)";
+ }
+ }
+ return out;
+}
+
namespace {
-LazyInstance<ThreadLocalBoolean>::Leaky g_blocking_disallowed =
- LAZY_INSTANCE_INITIALIZER;
-
-LazyInstance<ThreadLocalBoolean>::Leaky
- g_singleton_disallowed = LAZY_INSTANCE_INITIALIZER;
-
-LazyInstance<ThreadLocalBoolean>::Leaky g_base_sync_primitives_disallowed =
- LAZY_INSTANCE_INITIALIZER;
-
-LazyInstance<ThreadLocalBoolean>::Leaky g_cpu_intensive_work_disallowed =
- LAZY_INSTANCE_INITIALIZER;
+// TODO(crbug.com/1423437): Change these to directly-accessed, namespace-scope
+// `thread_local BooleanWithStack`s when doing so doesn't cause crashes.
+BooleanWithStack& GetBlockingDisallowedTls() {
+ static NoDestructor<ThreadLocalOwnedPointer<BooleanWithStack>> instance;
+ auto& tls = *instance;
+ if (!tls.Get()) {
+ tls.Set(std::make_unique<BooleanWithStack>());
+ }
+ return *tls;
+}
+BooleanWithStack& GetSingletonDisallowedTls() {
+ static NoDestructor<ThreadLocalOwnedPointer<BooleanWithStack>> instance;
+ auto& tls = *instance;
+ if (!tls.Get()) {
+ tls.Set(std::make_unique<BooleanWithStack>());
+ }
+ return *tls;
+}
+BooleanWithStack& GetBaseSyncPrimitivesDisallowedTls() {
+ static NoDestructor<ThreadLocalOwnedPointer<BooleanWithStack>> instance;
+ auto& tls = *instance;
+ if (!tls.Get()) {
+ tls.Set(std::make_unique<BooleanWithStack>());
+ }
+ return *tls;
+}
+BooleanWithStack& GetCPUIntensiveWorkDisallowedTls() {
+ static NoDestructor<ThreadLocalOwnedPointer<BooleanWithStack>> instance;
+ auto& tls = *instance;
+ if (!tls.Get()) {
+ tls.Set(std::make_unique<BooleanWithStack>());
+ }
+ return *tls;
+}
} // namespace
+namespace internal {
+
void AssertBlockingAllowed() {
- DCHECK(!g_blocking_disallowed.Get().Get())
+ DCHECK(!GetBlockingDisallowedTls())
<< "Function marked as blocking was called from a scope that disallows "
- "blocking! If this task is running inside the TaskScheduler, it needs "
+ "blocking! If this task is running inside the ThreadPool, it needs "
"to have MayBlock() in its TaskTraits. Otherwise, consider making "
"this blocking work asynchronous or, as a last resort, you may use "
- "ScopedAllowBlocking (see its documentation for best practices).";
+ "ScopedAllowBlocking (see its documentation for best practices).\n"
+ << "blocking_disallowed " << GetBlockingDisallowedTls();
}
+void AssertBlockingDisallowedForTesting() {
+ DCHECK(GetBlockingDisallowedTls())
+ << "blocking_disallowed " << GetBlockingDisallowedTls();
+}
+
+} // namespace internal
+
void DisallowBlocking() {
- g_blocking_disallowed.Get().Set(true);
+ GetBlockingDisallowedTls() = BooleanWithStack(true);
}
ScopedDisallowBlocking::ScopedDisallowBlocking()
- : was_disallowed_(g_blocking_disallowed.Get().Get()) {
- g_blocking_disallowed.Get().Set(true);
-}
+ : resetter_(&GetBlockingDisallowedTls(), BooleanWithStack(true)) {}
ScopedDisallowBlocking::~ScopedDisallowBlocking() {
- DCHECK(g_blocking_disallowed.Get().Get());
- g_blocking_disallowed.Get().Set(was_disallowed_);
-}
-
-ScopedAllowBlocking::ScopedAllowBlocking()
- : was_disallowed_(g_blocking_disallowed.Get().Get()) {
- g_blocking_disallowed.Get().Set(false);
-}
-
-ScopedAllowBlocking::~ScopedAllowBlocking() {
- DCHECK(!g_blocking_disallowed.Get().Get());
- g_blocking_disallowed.Get().Set(was_disallowed_);
+ DCHECK(GetBlockingDisallowedTls())
+ << "~ScopedDisallowBlocking() running while surprisingly already no "
+ "longer disallowed.\n"
+ << "blocking_disallowed " << GetBlockingDisallowedTls();
}
void DisallowBaseSyncPrimitives() {
- g_base_sync_primitives_disallowed.Get().Set(true);
+ GetBaseSyncPrimitivesDisallowedTls() = BooleanWithStack(true);
+}
+
+ScopedDisallowBaseSyncPrimitives::ScopedDisallowBaseSyncPrimitives()
+ : resetter_(&GetBaseSyncPrimitivesDisallowedTls(), BooleanWithStack(true)) {
+}
+
+ScopedDisallowBaseSyncPrimitives::~ScopedDisallowBaseSyncPrimitives() {
+ DCHECK(GetBaseSyncPrimitivesDisallowedTls())
+ << "~ScopedDisallowBaseSyncPrimitives() running while surprisingly "
+ "already no longer disallowed.\n"
+ << "base_sync_primitives_disallowed "
+ << GetBaseSyncPrimitivesDisallowedTls();
}
ScopedAllowBaseSyncPrimitives::ScopedAllowBaseSyncPrimitives()
- : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
- DCHECK(!g_blocking_disallowed.Get().Get())
+ : resetter_(&GetBaseSyncPrimitivesDisallowedTls(),
+ BooleanWithStack(false)) {
+ DCHECK(!GetBlockingDisallowedTls())
<< "To allow //base sync primitives in a scope where blocking is "
- "disallowed use ScopedAllowBaseSyncPrimitivesOutsideBlockingScope.";
- g_base_sync_primitives_disallowed.Get().Set(false);
+ "disallowed use ScopedAllowBaseSyncPrimitivesOutsideBlockingScope.\n"
+ << "blocking_disallowed " << GetBlockingDisallowedTls();
}
ScopedAllowBaseSyncPrimitives::~ScopedAllowBaseSyncPrimitives() {
- DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
- g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
-}
-
-ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
- ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()
- : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
- g_base_sync_primitives_disallowed.Get().Set(false);
-}
-
-ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
- ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() {
- DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
- g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
+ DCHECK(!GetBaseSyncPrimitivesDisallowedTls())
+ << "~ScopedAllowBaseSyncPrimitives() running while surprisingly already "
+ "no longer allowed.\n"
+ << "base_sync_primitives_disallowed "
+ << GetBaseSyncPrimitivesDisallowedTls();
}
ScopedAllowBaseSyncPrimitivesForTesting::
ScopedAllowBaseSyncPrimitivesForTesting()
- : was_disallowed_(g_base_sync_primitives_disallowed.Get().Get()) {
- g_base_sync_primitives_disallowed.Get().Set(false);
-}
+ : resetter_(&GetBaseSyncPrimitivesDisallowedTls(),
+ BooleanWithStack(false)) {}
ScopedAllowBaseSyncPrimitivesForTesting::
~ScopedAllowBaseSyncPrimitivesForTesting() {
- DCHECK(!g_base_sync_primitives_disallowed.Get().Get());
- g_base_sync_primitives_disallowed.Get().Set(was_disallowed_);
+ DCHECK(!GetBaseSyncPrimitivesDisallowedTls())
+ << "~ScopedAllowBaseSyncPrimitivesForTesting() running while " // IN-TEST
+ "surprisingly already no longer allowed.\n"
+ << "base_sync_primitives_disallowed "
+ << GetBaseSyncPrimitivesDisallowedTls();
+}
+
+ScopedAllowUnresponsiveTasksForTesting::ScopedAllowUnresponsiveTasksForTesting()
+ : base_sync_resetter_(&GetBaseSyncPrimitivesDisallowedTls(),
+ BooleanWithStack(false)),
+ blocking_resetter_(&GetBlockingDisallowedTls(), BooleanWithStack(false)),
+ cpu_resetter_(&GetCPUIntensiveWorkDisallowedTls(),
+ BooleanWithStack(false)) {}
+
+ScopedAllowUnresponsiveTasksForTesting::
+ ~ScopedAllowUnresponsiveTasksForTesting() {
+ DCHECK(!GetBaseSyncPrimitivesDisallowedTls())
+ << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST
+ "surprisingly already no longer allowed.\n"
+ << "base_sync_primitives_disallowed "
+ << GetBaseSyncPrimitivesDisallowedTls();
+ DCHECK(!GetBlockingDisallowedTls())
+ << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST
+ "surprisingly already no longer allowed.\n"
+ << "blocking_disallowed " << GetBlockingDisallowedTls();
+ DCHECK(!GetCPUIntensiveWorkDisallowedTls())
+ << "~ScopedAllowUnresponsiveTasksForTesting() running while " // IN-TEST
+ "surprisingly already no longer allowed.\n"
+ << "cpu_intensive_work_disallowed " << GetCPUIntensiveWorkDisallowedTls();
}
namespace internal {
void AssertBaseSyncPrimitivesAllowed() {
- DCHECK(!g_base_sync_primitives_disallowed.Get().Get())
+ DCHECK(!GetBaseSyncPrimitivesDisallowedTls())
<< "Waiting on a //base sync primitive is not allowed on this thread to "
"prevent jank and deadlock. If waiting on a //base sync primitive is "
"unavoidable, do it within the scope of a "
- "ScopedAllowBaseSyncPrimitives. If in a test, "
- "use ScopedAllowBaseSyncPrimitivesForTesting.";
+ "ScopedAllowBaseSyncPrimitives. If in a test, use "
+ "ScopedAllowBaseSyncPrimitivesForTesting.\n"
+ << "base_sync_primitives_disallowed "
+ << GetBaseSyncPrimitivesDisallowedTls()
+ << "It can be useful to know that blocking_disallowed is "
+ << GetBlockingDisallowedTls();
}
void ResetThreadRestrictionsForTesting() {
- g_blocking_disallowed.Get().Set(false);
- g_singleton_disallowed.Get().Set(false);
- g_base_sync_primitives_disallowed.Get().Set(false);
- g_cpu_intensive_work_disallowed.Get().Set(false);
+ GetBlockingDisallowedTls() = BooleanWithStack(false);
+ GetSingletonDisallowedTls() = BooleanWithStack(false);
+ GetBaseSyncPrimitivesDisallowedTls() = BooleanWithStack(false);
+ GetCPUIntensiveWorkDisallowedTls() = BooleanWithStack(false);
+}
+
+void AssertSingletonAllowed() {
+ DCHECK(!GetSingletonDisallowedTls())
+ << "LazyInstance/Singleton is not allowed to be used on this thread. "
+ "Most likely it's because this thread is not joinable (or the current "
+ "task is running with TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN "
+ "semantics), so AtExitManager may have deleted the object on "
+ "shutdown, leading to a potential shutdown crash. If you need to use "
+ "the object from this context, it'll have to be updated to use Leaky "
+ "traits.\n"
+ << "singleton_disallowed " << GetSingletonDisallowedTls();
}
} // namespace internal
+void DisallowSingleton() {
+ GetSingletonDisallowedTls() = BooleanWithStack(true);
+}
+
+#if defined(STARBOARD)
+bool GetSingletonAllowed() {
+ return !!GetSingletonDisallowedTls();
+}
+#endif
+
+ScopedDisallowSingleton::ScopedDisallowSingleton()
+ : resetter_(&GetSingletonDisallowedTls(), BooleanWithStack(true)) {}
+
+ScopedDisallowSingleton::~ScopedDisallowSingleton() {
+ DCHECK(GetSingletonDisallowedTls())
+ << "~ScopedDisallowSingleton() running while surprisingly already no "
+ "longer disallowed.\n"
+ << "singleton_disallowed " << GetSingletonDisallowedTls();
+}
+
void AssertLongCPUWorkAllowed() {
- DCHECK(!g_cpu_intensive_work_disallowed.Get().Get())
+ DCHECK(!GetCPUIntensiveWorkDisallowedTls())
<< "Function marked as CPU intensive was called from a scope that "
- "disallows this kind of work! Consider making this work asynchronous.";
+ "disallows this kind of work! Consider making this work "
+ "asynchronous.\n"
+ << "cpu_intensive_work_disallowed " << GetCPUIntensiveWorkDisallowedTls();
}
void DisallowUnresponsiveTasks() {
DisallowBlocking();
DisallowBaseSyncPrimitives();
- g_cpu_intensive_work_disallowed.Get().Set(true);
-}
-
-ThreadRestrictions::ScopedAllowIO::ScopedAllowIO()
- : was_allowed_(SetIOAllowed(true)) {}
-
-ThreadRestrictions::ScopedAllowIO::~ScopedAllowIO() {
- SetIOAllowed(was_allowed_);
+ GetCPUIntensiveWorkDisallowedTls() = BooleanWithStack(true);
}
// static
-bool ThreadRestrictions::SetIOAllowed(bool allowed) {
- bool previous_disallowed = g_blocking_disallowed.Get().Get();
- g_blocking_disallowed.Get().Set(!allowed);
- return !previous_disallowed;
-}
-
-#if defined(STARBOARD)
-// static
-bool ThreadRestrictions::GetSingletonAllowed() {
- return !g_singleton_disallowed.Get().Get();
-}
-#endif // defined(STARBOARD)
-
-// static
-bool ThreadRestrictions::SetSingletonAllowed(bool allowed) {
- bool previous_disallowed = g_singleton_disallowed.Get().Get();
- g_singleton_disallowed.Get().Set(!allowed);
- return !previous_disallowed;
+void PermanentThreadAllowance::AllowBlocking() {
+ GetBlockingDisallowedTls() = BooleanWithStack(false);
}
// static
-void ThreadRestrictions::AssertSingletonAllowed() {
- if (g_singleton_disallowed.Get().Get()) {
- NOTREACHED() << "LazyInstance/Singleton is not allowed to be used on this "
- << "thread. Most likely it's because this thread is not "
- << "joinable (or the current task is running with "
- << "TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN semantics), so "
- << "AtExitManager may have deleted the object on shutdown, "
- << "leading to a potential shutdown crash. If you need to use "
- << "the object from this context, it'll have to be updated to "
- << "use Leaky traits.";
- }
-}
-
-// static
-void ThreadRestrictions::DisallowWaiting() {
- DisallowBaseSyncPrimitives();
-}
-
-bool ThreadRestrictions::SetWaitAllowed(bool allowed) {
- bool previous_disallowed = g_base_sync_primitives_disallowed.Get().Get();
- g_base_sync_primitives_disallowed.Get().Set(!allowed);
- return !previous_disallowed;
-}
-
-ThreadRestrictions::ScopedAllowWait::ScopedAllowWait()
- : was_allowed_(SetWaitAllowed(true)) {}
-
-ThreadRestrictions::ScopedAllowWait::~ScopedAllowWait() {
- SetWaitAllowed(was_allowed_);
+void PermanentThreadAllowance::AllowBaseSyncPrimitives() {
+ GetBaseSyncPrimitivesDisallowedTls() = BooleanWithStack(false);
}
} // namespace base
#endif // DCHECK_IS_ON()
+
+namespace base {
+
+ScopedAllowBlocking::ScopedAllowBlocking(const Location& from_here)
+#if DCHECK_IS_ON()
+ : resetter_(&GetBlockingDisallowedTls(), BooleanWithStack(false))
+#endif
+{
+ TRACE_EVENT_BEGIN(
+ "base", "ScopedAllowBlocking", [&](perfetto::EventContext ctx) {
+ ctx.event()->set_source_location_iid(
+ base::trace_event::InternedSourceLocation::Get(&ctx, from_here));
+ });
+}
+
+ScopedAllowBlocking::~ScopedAllowBlocking() {
+ TRACE_EVENT_END0("base", "ScopedAllowBlocking");
+
+#if DCHECK_IS_ON()
+ DCHECK(!GetBlockingDisallowedTls())
+ << "~ScopedAllowBlocking() running while surprisingly already no longer "
+ "allowed.\n"
+ << "blocking_disallowed " << GetBlockingDisallowedTls();
+#endif
+}
+
+ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(const Location& from_here)
+#if DCHECK_IS_ON()
+ : resetter_(&GetBaseSyncPrimitivesDisallowedTls(), BooleanWithStack(false))
+#endif
+{
+ TRACE_EVENT_BEGIN(
+ "base", "ScopedAllowBaseSyncPrimitivesOutsideBlockingScope",
+ [&](perfetto::EventContext ctx) {
+ ctx.event()->set_source_location_iid(
+ base::trace_event::InternedSourceLocation::Get(&ctx, from_here));
+ });
+
+ // Since this object is used to indicate that sync primitives will be used to
+ // wait for an event ignore the current operation for hang watching purposes
+ // since the wait time duration is unknown.
+ base::HangWatcher::InvalidateActiveExpectations();
+}
+
+ScopedAllowBaseSyncPrimitivesOutsideBlockingScope::
+ ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() {
+ TRACE_EVENT_END0("base", "ScopedAllowBaseSyncPrimitivesOutsideBlockingScope");
+
+#if DCHECK_IS_ON()
+ DCHECK(!GetBaseSyncPrimitivesDisallowedTls())
+ << "~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope() running while "
+ "surprisingly already no longer allowed.\n"
+ << "base_sync_primitives_disallowed "
+ << GetBaseSyncPrimitivesDisallowedTls();
+#endif
+}
+
+} // namespace base
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index 603fed9..bb14c47 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -1,228 +1,106 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_THREADING_THREAD_RESTRICTIONS_H_
#define BASE_THREADING_THREAD_RESTRICTIONS_H_
+#include "base/auto_reset.h"
#include "base/base_export.h"
+#include "base/compiler_specific.h"
+#include "base/dcheck_is_on.h"
#include "base/gtest_prod_util.h"
-#include "base/logging.h"
-#include "base/macros.h"
-
-class BrowserProcessImpl;
-class HistogramSynchronizer;
-class NativeBackendKWallet;
-class KeyStorageLinux;
-
-namespace android_webview {
-class AwFormDatabaseService;
-class CookieManager;
-class ScopedAllowInitGLBindings;
-}
-namespace audio {
-class OutputDevice;
-}
-namespace blink {
-class VideoFrameResourceProvider;
-}
-namespace cc {
-class CompletionEvent;
-class SingleThreadTaskGraphRunner;
-}
-namespace chromeos {
-class BlockingMethodCaller;
-namespace system {
-class StatisticsProviderImpl;
-}
-}
-namespace chrome_browser_net {
-class Predictor;
-}
-namespace content {
-class BrowserGpuChannelHostFactory;
-class BrowserMainLoop;
-class BrowserProcessSubThread;
-class BrowserShutdownProfileDumper;
-class BrowserTestBase;
-class CategorizedWorkerPool;
-class GpuProcessTransportFactory;
-class NestedMessagePumpAndroid;
-class ScopedAllowWaitForAndroidLayoutTests;
-class ScopedAllowWaitForDebugURL;
-class SessionStorageDatabase;
-class SoftwareOutputDeviceMus;
-class ServiceWorkerSubresourceLoader;
-class SynchronousCompositor;
-class SynchronousCompositorHost;
-class SynchronousCompositorSyncCallBridge;
-class TextInputClientMac;
-} // namespace content
-namespace cronet {
-class CronetPrefsManager;
-class CronetURLRequestContext;
-} // namespace cronet
-namespace dbus {
-class Bus;
-}
-namespace disk_cache {
-class BackendImpl;
-class InFlightIO;
-}
-namespace functions {
-class ExecScriptScopedAllowBaseSyncPrimitives;
-}
-namespace gpu {
-class GpuChannelHost;
-}
-namespace leveldb {
-class LevelDBMojoProxy;
-}
-namespace media {
-class AudioInputDevice;
-class BlockingUrlProtocol;
-}
-namespace midi {
-class TaskService; // https://crbug.com/796830
-}
-namespace mojo {
-class CoreLibraryInitializer;
-class SyncCallRestrictions;
-namespace core {
-class ScopedIPCSupport;
-}
-}
-namespace rlz_lib {
-class FinancialPing;
-}
-namespace ui {
-class CommandBufferClientImpl;
-class CommandBufferLocal;
-class GpuState;
-class MaterialDesignController;
-}
-namespace net {
-class MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
-class NetworkChangeNotifierMac;
-namespace internal {
-class AddressTrackerLinux;
-}
-}
-
-namespace remoting {
-class AutoThread;
-}
-
-namespace resource_coordinator {
-class TabManagerDelegate;
-}
-
-namespace service_manager {
-class ServiceProcessLauncher;
-}
-
-namespace shell_integration_linux {
-class LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
-}
-
-namespace ui {
-class WindowResizeHelperMac;
-}
-
-namespace viz {
-class HostGpuMemoryBufferManager;
-}
-
-namespace webrtc {
-class DesktopConfigurationMonitor;
-}
-
-namespace base {
-
-namespace android {
-class JavaHandlerThread;
-}
-
-namespace internal {
-class TaskTracker;
-}
-
-class AdjustOOMScoreHelper;
-class GetAppOutputScopedAllowBaseSyncPrimitives;
-class SimpleThread;
-class StackSamplingProfiler;
-class Thread;
-class ThreadTestHelper;
+#include "base/location.h"
+#include "build/build_config.h"
#if DCHECK_IS_ON()
-#define INLINE_IF_DCHECK_IS_OFF BASE_EXPORT
-#define EMPTY_BODY_IF_DCHECK_IS_OFF
-#else
-#define INLINE_IF_DCHECK_IS_OFF inline
-#define EMPTY_BODY_IF_DCHECK_IS_OFF \
- {}
+#include "base/debug/stack_trace.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
#endif
-// A "blocking call" refers to any call that causes the calling thread to wait
-// off-CPU. It includes but is not limited to calls that wait on synchronous
-// file I/O operations: read or write a file from disk, interact with a pipe or
-// a socket, rename or delete a file, enumerate files in a directory, etc.
-// Acquiring a low contention lock is not considered a blocking call.
-
-// Asserts that blocking calls are allowed in the current scope. Prefer using
-// ScopedBlockingCall instead, which also serves as a precise annotation of the
-// scope that may/will block.
+// -----------------------------------------------------------------------------
+// Usage documentation
+// -----------------------------------------------------------------------------
//
-// Style tip: It's best if you put AssertBlockingAllowed() checks as close to
-// the blocking call as possible. For example:
+// Overview:
+// This file exposes functions to ban and allow certain slow operations
+// on a per-thread basis. To annotate *usage* of such slow operations, refer to
+// scoped_blocking_call.h instead.
//
-// void ReadFile() {
-// PreWork();
+// Specific allowances that can be controlled in this file are:
//
-// base::AssertBlockingAllowed();
-// fopen(...);
+// - Blocking call: Refers to any call that causes the calling thread to wait
+// off-CPU. It includes but is not limited to calls that wait on synchronous
+// file I/O operations: read or write a file from disk, interact with a pipe
+// or a socket, rename or delete a file, enumerate files in a directory, etc.
+// Acquiring a low contention lock is not considered a blocking call.
//
-// PostWork();
-// }
+// Prefer to allow a blocking call by posting a task to
+// base::ThreadPoolInstance with base::MayBlock().
//
-// void Bar() {
-// ReadFile();
-// }
+// - Waiting on a //base sync primitive: Refers to calling one of these methods:
+// - base::WaitableEvent::*Wait*
+// - base::ConditionVariable::*Wait*
+// - base::Process::WaitForExit*
//
-// void Foo() {
-// Bar();
-// }
-INLINE_IF_DCHECK_IS_OFF void AssertBlockingAllowed()
- EMPTY_BODY_IF_DCHECK_IS_OFF;
-
-// Disallows blocking on the current thread.
-INLINE_IF_DCHECK_IS_OFF void DisallowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
-
-// Disallows blocking calls within its scope.
-class BASE_EXPORT ScopedDisallowBlocking {
- public:
- ScopedDisallowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
- ~ScopedDisallowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
-
- private:
-#if DCHECK_IS_ON()
- const bool was_disallowed_;
-#endif
-
- DISALLOW_COPY_AND_ASSIGN(ScopedDisallowBlocking);
-};
-
-// ScopedAllowBlocking(ForTesting) allow blocking calls within a scope where
-// they are normally disallowed.
+// Prefer not to wait on //base sync primitives (see below for alternatives).
+// When it is unavoidable, use ScopedAllowBaseSyncPrimitives in a task posted
+// to base::ThreadPoolInstance with base::MayBlock().
//
-// Avoid using this. Prefer making blocking calls from tasks posted to
-// base::TaskScheduler with base::MayBlock().
+// - Accessing singletons: Accessing global state (Singleton / LazyInstance) is
+// problematic on threads whom aren't joined on shutdown as they can be using
+// the state as it becomes invalid during tear down. base::NoDestructor is the
+// preferred alternative for global state and doesn't have this restriction.
+//
+// - Long CPU work: Refers to any code that takes more than 100 ms to
+// run when there is no CPU contention and no hard page faults and therefore,
+// is not suitable to run on a thread required to keep the browser responsive
+// (where jank could be visible to the user).
+//
+// The following disallowance functions are offered:
+// - DisallowBlocking(): Disallows blocking calls on the current thread.
+// - DisallowBaseSyncPrimitives(): Disallows waiting on a //base sync primitive
+// on the current thread.
+// - DisallowSingleton(): Disallows using singletons on the current thread.
+// - DisallowUnresponsiveTasks(): Disallows blocking calls, waiting on a //base
+// sync primitive, and long CPU work on the current thread.
+//
+// In addition, scoped-allowance mechanisms are offered to make an exception
+// within a scope for a behavior that is normally disallowed.
+// - ScopedAllowBlocking: Allows blocking calls. Prefer to use base::MayBlock()
+// instead.
+// - ScopedAllowBaseSyncPrimitives: Allows waiting on a //base sync primitive.
+// Must also be in a scope where blocking calls are allowed.
+// - ScopedAllowBaseSyncPrimitivesOutsideBlockingScope: Allow waiting on a
+// //base sync primitive, even in a scope where blocking calls are
+// disallowed. Prefer to use a combination of base::MayBlock() and
+// ScopedAllowBaseSyncPrimitives.
+//
+// Avoid using allowances outside of unit tests. In unit tests, use allowances
+// with the suffix "ForTesting":
+// - ScopedAllowBlockingForTesting: Allows blocking calls in unit tests.
+// - ScopedAllowBaseSyncPrimitivesForTesting: Allows waiting on a //base sync
+// primitive in unit tests. For convenience this can be used in a scope
+// where blocking calls are disallowed. Note that base::TestWaitableEvent can
+// be used without this, also for convenience.
+//
+// Prefer making blocking calls from tasks posted to base::ThreadPoolInstance
+// with base::MayBlock().
+//
+// Instead of waiting on a WaitableEvent or a ConditionVariable, prefer putting
+// the work that should happen after the wait in a continuation callback and
+// post it from where the WaitableEvent or ConditionVariable would have been
+// signaled. If something needs to be scheduled after many tasks have executed,
+// use base::BarrierClosure.
+//
+// On Windows, join processes asynchronously using base::win::ObjectWatcher.
//
// Where unavoidable, put ScopedAllow* instances in the narrowest scope possible
-// in the caller making the blocking call but no further down. That is: if a
+// in the caller making the blocking call but no further down. For example: if a
// Cleanup() method needs to do a blocking call, document Cleanup() as blocking
// and add a ScopedAllowBlocking instance in callers that can't avoid making
// this call from a context where blocking is banned, as such:
+//
// void Client::MyMethod() {
// (...)
// {
@@ -246,72 +124,609 @@
// somehow knows that in practice this will not block), it might be okay to hide
// the ScopedAllowBlocking instance in the impl with a comment explaining why
// that's okay.
-class BASE_EXPORT ScopedAllowBlocking {
- private:
- // This can only be instantiated by friends. Use ScopedAllowBlockingForTesting
- // in unit tests to avoid the friend requirement.
- FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest, ScopedAllowBlocking);
- friend class AdjustOOMScoreHelper;
- friend class android_webview::ScopedAllowInitGLBindings;
- friend class audio::OutputDevice;
- friend class content::BrowserProcessSubThread;
- friend class content::GpuProcessTransportFactory;
- friend class cronet::CronetPrefsManager;
- friend class cronet::CronetURLRequestContext;
- friend class media::AudioInputDevice;
- friend class mojo::CoreLibraryInitializer;
- friend class resource_coordinator::TabManagerDelegate; // crbug.com/778703
- friend class ui::MaterialDesignController;
- friend class ScopedAllowBlockingForTesting;
- friend class StackSamplingProfiler;
- ScopedAllowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
- ~ScopedAllowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+class BrowserProcessImpl;
+class BrowserThemePack;
+class ChromeNSSCryptoModuleDelegate;
+class DesktopNotificationBalloon;
+class FirefoxProfileLock;
+class KeyStorageLinux;
+class NativeBackendKWallet;
+class NativeDesktopMediaList;
+class Profile;
+class ProfileImpl;
+class StartupTabProviderImpl;
+class GaiaConfig;
+class WebEngineBrowserMainParts;
+class ScopedAllowBlockingForProfile;
+
+namespace base {
+class File;
+class FilePath;
+} // namespace base
+
+Profile* GetLastProfileMac();
+bool EnsureBrowserStateDirectoriesCreated(const base::FilePath&,
+ const base::FilePath&,
+ const base::FilePath&);
+
+namespace android_webview {
+class AwFormDatabaseService;
+class CookieManager;
+class JsSandboxIsolate;
+class ScopedAllowInitGLBindings;
+class VizCompositorThreadRunnerWebView;
+} // namespace android_webview
+namespace ash {
+class MojoUtils;
+class BrowserDataBackMigrator;
+class LoginEventRecorder;
+class StartupCustomizationDocument;
+class StartupUtils;
+bool CameraAppUIShouldEnableLocalOverride(const std::string&);
+namespace system {
+class StatisticsProviderImpl;
+} // namespace system
+} // namespace ash
+namespace audio {
+class OutputDevice;
+}
+namespace blink {
+class DiskDataAllocator;
+class IdentifiabilityActiveSampler;
+class RTCVideoDecoderAdapter;
+class RTCVideoEncoder;
+class SourceStream;
+class VideoFrameResourceProvider;
+class WebRtcVideoFrameAdapter;
+class LegacyWebRtcVideoFrameAdapter;
+class VideoTrackRecorderImplContextProvider;
+class WorkerThread;
+namespace scheduler {
+class NonMainThreadImpl;
+}
+} // namespace blink
+namespace cc {
+class CategorizedWorkerPoolImpl;
+class CategorizedWorkerPoolJob;
+class CategorizedWorkerPool;
+class CompletionEvent;
+class TileTaskManagerImpl;
+} // namespace cc
+namespace chrome {
+bool PathProvider(int, base::FilePath*);
+void SessionEnding();
+} // namespace chrome
+namespace chromecast {
+class CrashUtil;
+}
+namespace chromeos {
+class BlockingMethodCaller;
+namespace system {
+bool IsCoreSchedulingAvailable();
+int NumberOfPhysicalCores();
+} // namespace system
+} // namespace chromeos
+namespace chrome_cleaner {
+class ResetShortcutsComponent;
+class SystemReportComponent;
+} // namespace chrome_cleaner
+namespace content {
+class BrowserGpuChannelHostFactory;
+class BrowserMainLoop;
+class BrowserProcessIOThread;
+class BrowserTestBase;
+#if BUILDFLAG(IS_IOS)
+class ContentMainRunnerImpl;
+#endif // BUILDFLAG(IS_IOS)
+class DesktopCaptureDevice;
+class DWriteFontCollectionProxy;
+class DWriteFontProxyImpl;
+class EmergencyTraceFinalisationCoordinator;
+class InProcessUtilityThread;
+class NestedMessagePumpAndroid;
+class NetworkServiceInstancePrivate;
+class PepperPrintSettingsManagerImpl;
+class RenderProcessHostImpl;
+class RenderProcessHost;
+class RenderWidgetHostViewMac;
+class RendererBlinkPlatformImpl;
+class RTCVideoDecoder;
+class SandboxHostLinux;
+class ScopedAllowWaitForDebugURL;
+class ServiceWorkerContextClient;
+class ShellPathProvider;
+class SynchronousCompositor;
+class SynchronousCompositorHost;
+class SynchronousCompositorSyncCallBridge;
+class ScopedAllowBlockingForViewAura;
+class TextInputClientMac;
+class WebContentsImpl;
+class WebContentsViewMac;
+base::File CreateFileForDrop(base::FilePath*);
+} // namespace content
+namespace cronet {
+class CronetPrefsManager;
+class CronetContext;
+} // namespace cronet
+namespace crosapi {
+class LacrosThreadTypeDelegate;
+} // namespace crosapi
+namespace crypto {
+class ScopedAllowBlockingForNSS;
+}
+namespace dbus {
+class Bus;
+}
+namespace drive {
+class FakeDriveService;
+}
+namespace device {
+class UsbContext;
+}
+namespace discardable_memory {
+class ClientDiscardableSharedMemoryManager;
+}
+namespace disk_cache {
+class BackendImpl;
+class InFlightIO;
+bool CleanupDirectorySync(const base::FilePath&);
+} // namespace disk_cache
+namespace enterprise_connectors {
+class LinuxKeyRotationCommand;
+} // namespace enterprise_connectors
+namespace extensions {
+class InstalledLoader;
+class UnpackedInstaller;
+} // namespace extensions
+namespace font_service::internal {
+class MappedFontFile;
+}
+namespace functions {
+class ExecScriptScopedAllowBaseSyncPrimitives;
+}
+namespace gl {
+struct GLImplementationParts;
+namespace init {
+bool InitializeStaticGLBindings(GLImplementationParts);
+}
+} // namespace gl
+namespace history_report {
+class HistoryReportJniBridge;
+}
+namespace ios_web_view {
+class WebViewBrowserState;
+}
+namespace io_thread {
+class IOSIOThread;
+}
+namespace leveldb::port {
+class CondVar;
+} // namespace leveldb::port
+namespace nearby::chrome {
+class ScheduledExecutor;
+class SubmittableExecutor;
+} // namespace nearby::chrome
+namespace media {
+class AudioInputDevice;
+class AudioOutputDevice;
+class BlockingUrlProtocol;
+class FileVideoCaptureDeviceFactory;
+class MojoVideoEncodeAccelerator;
+class PaintCanvasVideoRenderer;
+} // namespace media
+namespace memory_instrumentation {
+class OSMetrics;
+}
+namespace memory_pressure {
+class UserLevelMemoryPressureSignalGenerator;
+}
+namespace metrics {
+class AndroidMetricsServiceClient;
+class CleanExitBeacon;
+} // namespace metrics
+namespace midi {
+class TaskService; // https://crbug.com/796830
+}
+namespace module_installer {
+class ScopedAllowModulePakLoad;
+}
+namespace mojo {
+class CoreLibraryInitializer;
+class SyncCallRestrictions;
+namespace core {
+class ScopedIPCSupport;
+namespace ipcz_driver {
+class MojoTrap;
+}
+} // namespace core
+} // namespace mojo
+namespace printing {
+class LocalPrinterHandlerDefault;
+#if BUILDFLAG(IS_MAC)
+class PrintBackendServiceImpl;
+#endif
+class PrintBackendServiceManager;
+class PrinterQuery;
+} // namespace printing
+namespace rlz_lib {
+class FinancialPing;
+}
+namespace storage {
+class ObfuscatedFileUtil;
+}
+namespace syncer {
+class GetLocalChangesRequest;
+class HttpBridge;
+} // namespace syncer
+namespace ui {
+class DrmThreadProxy;
+class DrmDisplayHostManager;
+class SelectFileDialogLinux;
+class ScopedAllowBlockingForGbmSurface;
+} // namespace ui
+namespace weblayer {
+class BrowserContextImpl;
+class ContentBrowserClientImpl;
+class ProfileImpl;
+class WebLayerPathProvider;
+} // namespace weblayer
+namespace net {
+class GSSAPISharedLibrary;
+class MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
+class MultiThreadedProxyResolverScopedAllowJoinOnIO;
+class NetworkChangeNotifierMac;
+class NetworkConfigWatcherMacThread;
+class ProxyConfigServiceWin;
+class ScopedAllowBlockingForSettingGetter;
+namespace internal {
+class AddressTrackerLinux;
+}
+} // namespace net
+
+namespace proxy_resolver {
+class ScopedAllowThreadJoinForProxyResolverV8Tracing;
+}
+
+namespace remote_cocoa {
+class DroppedScreenShotCopierMac;
+class SelectFileDialogBridge;
+} // namespace remote_cocoa
+
+namespace remoting {
+class AutoThread;
+class ScopedBypassIOThreadRestrictions;
+namespace protocol {
+class ScopedAllowSyncPrimitivesForWebRtcDataStreamAdapter;
+class ScopedAllowSyncPrimitivesForWebRtcTransport;
+class ScopedAllowThreadJoinForWebRtcTransport;
+} // namespace protocol
+} // namespace remoting
+
+namespace service_manager {
+class ServiceProcessLauncher;
+}
+
+namespace shell_integration_linux {
+class LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
+}
+
+namespace tracing {
+class FuchsiaPerfettoProducerConnector;
+}
+
+namespace ui {
+class WindowResizeHelperMac;
+}
+
+namespace updater {
+class SystemctlLauncherScopedAllowBaseSyncPrimitives;
+}
+
+namespace viz {
+class HostGpuMemoryBufferManager;
+class ClientGpuMemoryBufferManager;
+} // namespace viz
+
+namespace vr {
+class VrShell;
+}
+
+namespace web {
+class WebMainLoop;
+} // namespace web
+
+namespace webrtc {
+class DesktopConfigurationMonitor;
+}
+
+namespace base {
+class Environment;
+}
+
+bool HasWaylandDisplay(base::Environment* env);
+
+namespace base {
+
+namespace sequence_manager::internal {
+class TaskQueueImpl;
+} // namespace sequence_manager::internal
+
+namespace android {
+class JavaHandlerThread;
+class ScopedAllowBlockingForImportantFileWriter;
+} // namespace android
+
+namespace internal {
+class GetAppOutputScopedAllowBaseSyncPrimitives;
+class JobTaskSource;
+class TaskTracker;
+bool ReadProcFile(const FilePath& file, std::string* buffer);
+} // namespace internal
+
+namespace subtle {
+class PlatformSharedMemoryRegion;
+}
+
+namespace debug {
+class StackTrace;
+}
+
+namespace win {
+class OSInfo;
+class ScopedAllowBlockingForUserAccountControl;
+} // namespace win
+
+class AdjustOOMScoreHelper;
+class ChromeOSVersionInfo;
+class FileDescriptorWatcher;
+class FilePath;
+class Process;
+class ScopedAllowBlockingForProc;
+class ScopedAllowBlockingForProcessMetrics;
+class ScopedAllowThreadRecallForStackSamplingProfiler;
+class SimpleThread;
+class StackSamplingProfiler;
+class TestCustomDisallow;
+class Thread;
+
+void GetNSExecutablePath(base::FilePath* path);
#if DCHECK_IS_ON()
- const bool was_disallowed_;
-#endif
+// NOT_TAIL_CALLED if dcheck-is-on so it's always evident who irrevocably
+// altered the allowance (dcheck-builds will provide the setter's stack on
+// assertion) or who made a failing Assert*() call.
+#define INLINE_OR_NOT_TAIL_CALLED NOT_TAIL_CALLED BASE_EXPORT
+#define EMPTY_BODY_IF_DCHECK_IS_OFF
+#define DEFAULT_IF_DCHECK_IS_OFF
- DISALLOW_COPY_AND_ASSIGN(ScopedAllowBlocking);
+class BooleanWithStack {
+ public:
+ // Default value.
+ BooleanWithStack() = default;
+
+ // Value when explicitly set.
+ explicit BooleanWithStack(bool value);
+
+ explicit operator bool() const { return value_; }
+
+ friend std::ostream& operator<<(std::ostream& out,
+ const BooleanWithStack& bws);
+
+ private:
+ bool value_ = false;
+ absl::optional<debug::StackTrace> stack_;
};
-class ScopedAllowBlockingForTesting {
+#else
+// inline if dcheck-is-off so it's no overhead
+#define INLINE_OR_NOT_TAIL_CALLED inline
+
+// The static_assert() eats follow-on semicolons.
+#define EMPTY_BODY_IF_DCHECK_IS_OFF \
+ {} \
+ static_assert(true)
+
+#define DEFAULT_IF_DCHECK_IS_OFF = default
+#endif // DCHECK_IS_ON()
+
+namespace internal {
+
+// Asserts that blocking calls are allowed in the current scope. This is an
+// internal call, external code should use ScopedBlockingCall instead, which
+// serves as a precise annotation of the scope that may/will block.
+INLINE_OR_NOT_TAIL_CALLED void AssertBlockingAllowed()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+INLINE_OR_NOT_TAIL_CALLED void AssertBlockingDisallowedForTesting()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+} // namespace internal
+
+// Disallows blocking on the current thread.
+INLINE_OR_NOT_TAIL_CALLED void DisallowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+// Disallows blocking calls within its scope.
+class BASE_EXPORT [[maybe_unused, nodiscard]] ScopedDisallowBlocking {
public:
- ScopedAllowBlockingForTesting() {}
- ~ScopedAllowBlockingForTesting() {}
+ ScopedDisallowBlocking() DEFAULT_IF_DCHECK_IS_OFF;
+
+ ScopedDisallowBlocking(const ScopedDisallowBlocking&) = delete;
+ ScopedDisallowBlocking& operator=(const ScopedDisallowBlocking&) = delete;
+
+ ~ScopedDisallowBlocking() DEFAULT_IF_DCHECK_IS_OFF;
+
+ private:
+#if DCHECK_IS_ON()
+ const AutoReset<BooleanWithStack> resetter_;
+#endif
+};
+
+class BASE_EXPORT [[maybe_unused, nodiscard]] ScopedAllowBlocking {
+ public:
+ ScopedAllowBlocking(const ScopedAllowBlocking&) = delete;
+ ScopedAllowBlocking& operator=(const ScopedAllowBlocking&) = delete;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest,
+ NestedAllowRestoresPreviousStack);
+ FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest, ScopedAllowBlocking);
+ friend class ScopedAllowBlockingForTesting;
+
+ // This can only be instantiated by friends. Use ScopedAllowBlockingForTesting
+ // in unit tests to avoid the friend requirement.
+ // Sorted by class name (with namespace), #if blocks at the bottom.
+ friend class ::BrowserThemePack; // http://crbug.com/80206
+ friend class ::DesktopNotificationBalloon;
+ friend class ::FirefoxProfileLock;
+ friend class ::GaiaConfig;
+ friend class ::ProfileImpl;
+ friend class ::ScopedAllowBlockingForProfile;
+ friend class ::StartupTabProviderImpl;
+ friend class ::WebEngineBrowserMainParts;
+ friend class android_webview::ScopedAllowInitGLBindings;
+ friend class ash::BrowserDataBackMigrator;
+ friend class ash::LoginEventRecorder;
+ friend class ash::MojoUtils; // http://crbug.com/1055467
+ friend class ash::StartupCustomizationDocument; // http://crosbug.com/11103
+ friend class ash::StartupUtils;
+ friend class base::AdjustOOMScoreHelper;
+ friend class base::ChromeOSVersionInfo;
+ friend class base::Process;
+ friend class base::ScopedAllowBlockingForProc;
+ friend class base::ScopedAllowBlockingForProcessMetrics;
+ friend class base::StackSamplingProfiler;
+ friend class base::android::ScopedAllowBlockingForImportantFileWriter;
+ friend class base::debug::StackTrace;
+ friend class base::subtle::PlatformSharedMemoryRegion;
+ friend class base::win::ScopedAllowBlockingForUserAccountControl;
+ friend class blink::DiskDataAllocator;
+ friend class chromecast::CrashUtil;
+ friend class content::BrowserProcessIOThread;
+ friend class content::DWriteFontProxyImpl;
+ friend class content::NetworkServiceInstancePrivate;
+ friend class content::PepperPrintSettingsManagerImpl;
+ friend class content::RenderProcessHostImpl;
+ friend class content::RenderWidgetHostViewMac; // http://crbug.com/121917
+ friend class content::
+ ScopedAllowBlockingForViewAura; // http://crbug.com/332579
+ friend class content::ShellPathProvider;
+ friend class content::WebContentsViewMac;
+ friend class cronet::CronetContext;
+ friend class cronet::CronetPrefsManager;
+ friend class crosapi::LacrosThreadTypeDelegate;
+ friend class crypto::ScopedAllowBlockingForNSS; // http://crbug.com/59847
+ friend class drive::FakeDriveService;
+ friend class extensions::InstalledLoader;
+ friend class extensions::UnpackedInstaller;
+ friend class font_service::internal::MappedFontFile;
+ friend class ios_web_view::WebViewBrowserState;
+ friend class io_thread::IOSIOThread;
+ friend class media::FileVideoCaptureDeviceFactory;
+ friend class memory_instrumentation::OSMetrics;
+ friend class memory_pressure::UserLevelMemoryPressureSignalGenerator;
+ friend class metrics::AndroidMetricsServiceClient;
+ friend class metrics::CleanExitBeacon;
+ friend class module_installer::ScopedAllowModulePakLoad;
+ friend class mojo::CoreLibraryInitializer;
+ friend class net::GSSAPISharedLibrary; // http://crbug.com/66702
+ friend class net::ProxyConfigServiceWin; // http://crbug.com/61453
+ friend class net::
+ ScopedAllowBlockingForSettingGetter; // http://crbug.com/69057
+ friend class printing::LocalPrinterHandlerDefault;
+ friend class printing::PrintBackendServiceManager;
+ friend class printing::PrinterQuery;
+ friend class remote_cocoa::
+ DroppedScreenShotCopierMac; // https://crbug.com/1148078
+ friend class remote_cocoa::SelectFileDialogBridge;
+ friend class remoting::
+ ScopedBypassIOThreadRestrictions; // http://crbug.com/1144161
+ friend class ui::DrmDisplayHostManager;
+ friend class ui::ScopedAllowBlockingForGbmSurface;
+ friend class ui::SelectFileDialogLinux;
+ friend class weblayer::BrowserContextImpl;
+ friend class weblayer::ContentBrowserClientImpl;
+ friend class weblayer::ProfileImpl;
+ friend class weblayer::WebLayerPathProvider;
+#if BUILDFLAG(IS_MAC)
+ friend class printing::PrintBackendServiceImpl;
+#endif
+#if BUILDFLAG(IS_WIN)
+ friend class base::win::OSInfo;
+ friend class content::WebContentsImpl; // http://crbug.com/1262162
+#endif
+
+ // Sorted by function name (with namespace), ignoring the return type.
+ friend bool ::EnsureBrowserStateDirectoriesCreated(const base::FilePath&,
+ const base::FilePath&,
+ const base::FilePath&);
+ friend Profile* ::GetLastProfileMac(); // http://crbug.com/1176734
+ friend bool ::HasWaylandDisplay(
+ base::Environment* env); // http://crbug.com/1246928
+ friend bool ash::CameraAppUIShouldEnableLocalOverride(const std::string&);
+ friend void base::GetNSExecutablePath(base::FilePath*);
+ friend bool base::internal::ReadProcFile(const FilePath& file,
+ std::string* buffer);
+ friend bool chrome::PathProvider(int,
+ base::FilePath*); // http://crbug.com/259796
+ friend void chrome::SessionEnding();
+ friend bool chromeos::system::IsCoreSchedulingAvailable();
+ friend int chromeos::system::NumberOfPhysicalCores();
+ friend base::File content::CreateFileForDrop(
+ base::FilePath* file_path); // http://crbug.com/110709
+ friend bool disk_cache::CleanupDirectorySync(const base::FilePath&);
+ friend bool gl::init::InitializeStaticGLBindings(gl::GLImplementationParts);
+
+ ScopedAllowBlocking(const Location& from_here = Location::Current());
+ ~ScopedAllowBlocking();
+
+#if DCHECK_IS_ON()
+ const AutoReset<BooleanWithStack> resetter_;
+#endif
+};
+
+class [[maybe_unused, nodiscard]] ScopedAllowBlockingForTesting {
+ public:
+ ScopedAllowBlockingForTesting() = default;
+
+ ScopedAllowBlockingForTesting(const ScopedAllowBlockingForTesting&) = delete;
+ ScopedAllowBlockingForTesting& operator=(
+ const ScopedAllowBlockingForTesting&) = delete;
+
+ ~ScopedAllowBlockingForTesting() = default;
private:
#if DCHECK_IS_ON()
ScopedAllowBlocking scoped_allow_blocking_;
#endif
-
- DISALLOW_COPY_AND_ASSIGN(ScopedAllowBlockingForTesting);
};
-// "Waiting on a //base sync primitive" refers to calling one of these methods:
-// - base::WaitableEvent::*Wait*
-// - base::ConditionVariable::*Wait*
-// - base::Process::WaitForExit*
-
-// Disallows waiting on a //base sync primitive on the current thread.
-INLINE_IF_DCHECK_IS_OFF void DisallowBaseSyncPrimitives()
+INLINE_OR_NOT_TAIL_CALLED void DisallowBaseSyncPrimitives()
EMPTY_BODY_IF_DCHECK_IS_OFF;
-// ScopedAllowBaseSyncPrimitives(ForTesting)(OutsideBlockingScope) allow waiting
-// on a //base sync primitive within a scope where this is normally disallowed.
-//
-// Avoid using this.
-//
-// Instead of waiting on a WaitableEvent or a ConditionVariable, put the work
-// that should happen after the wait in a callback and post that callback from
-// where the WaitableEvent or ConditionVariable would have been signaled. If
-// something needs to be scheduled after many tasks have executed, use
-// base::BarrierClosure.
-//
-// On Windows, join processes asynchronously using base::win::ObjectWatcher.
+// Disallows singletons within its scope.
+class BASE_EXPORT [[maybe_unused, nodiscard]] ScopedDisallowBaseSyncPrimitives {
+ public:
+ ScopedDisallowBaseSyncPrimitives() DEFAULT_IF_DCHECK_IS_OFF;
-// This can only be used in a scope where blocking is allowed.
-class BASE_EXPORT ScopedAllowBaseSyncPrimitives {
+ ScopedDisallowBaseSyncPrimitives(const ScopedDisallowBaseSyncPrimitives&) =
+ delete;
+ ScopedDisallowBaseSyncPrimitives& operator=(
+ const ScopedDisallowBaseSyncPrimitives&) = delete;
+
+ ~ScopedDisallowBaseSyncPrimitives() DEFAULT_IF_DCHECK_IS_OFF;
+
+ private:
+#if DCHECK_IS_ON()
+ const AutoReset<BooleanWithStack> resetter_;
+#endif
+};
+
+class BASE_EXPORT [[maybe_unused, nodiscard]] ScopedAllowBaseSyncPrimitives {
+ public:
+ ScopedAllowBaseSyncPrimitives(const ScopedAllowBaseSyncPrimitives&) = delete;
+ ScopedAllowBaseSyncPrimitives& operator=(
+ const ScopedAllowBaseSyncPrimitives&) = delete;
+
private:
// This can only be instantiated by friends. Use
// ScopedAllowBaseSyncPrimitivesForTesting in unit tests to avoid the friend
@@ -322,33 +737,75 @@
ScopedAllowBaseSyncPrimitivesResetsState);
FRIEND_TEST_ALL_PREFIXES(ThreadRestrictionsTest,
ScopedAllowBaseSyncPrimitivesWithBlockingDisallowed);
- friend class base::GetAppOutputScopedAllowBaseSyncPrimitives;
- friend class content::BrowserProcessSubThread;
- friend class content::SessionStorageDatabase;
+
+ // Allowed usage:
+ // Sorted by class name (with namespace).
+ friend class ::ChromeNSSCryptoModuleDelegate;
+ friend class ::tracing::FuchsiaPerfettoProducerConnector;
+ friend class android_webview::JsSandboxIsolate;
+ friend class base::SimpleThread;
+ friend class base::internal::GetAppOutputScopedAllowBaseSyncPrimitives;
+ friend class blink::IdentifiabilityActiveSampler;
+ friend class blink::SourceStream;
+ friend class blink::VideoTrackRecorderImplContextProvider;
+ friend class blink::WorkerThread;
+ friend class blink::scheduler::NonMainThreadImpl;
+ friend class cc::CategorizedWorkerPoolImpl;
+ friend class cc::CategorizedWorkerPoolJob;
+ friend class chrome_cleaner::ResetShortcutsComponent;
+ friend class chrome_cleaner::SystemReportComponent;
+ friend class content::BrowserMainLoop;
+ friend class content::BrowserProcessIOThread;
+ friend class content::DWriteFontCollectionProxy;
+ friend class content::RendererBlinkPlatformImpl;
+ friend class content::ServiceWorkerContextClient;
+ friend class device::UsbContext;
+ friend class enterprise_connectors::LinuxKeyRotationCommand;
friend class functions::ExecScriptScopedAllowBaseSyncPrimitives;
- friend class leveldb::LevelDBMojoProxy;
+ friend class history_report::HistoryReportJniBridge;
+ friend class internal::TaskTracker;
+ friend class leveldb::port::CondVar;
+ friend class nearby::chrome::ScheduledExecutor;
+ friend class nearby::chrome::SubmittableExecutor;
+ friend class media::AudioOutputDevice;
friend class media::BlockingUrlProtocol;
+ friend class media::MojoVideoEncodeAccelerator;
friend class mojo::core::ScopedIPCSupport;
friend class net::MultiThreadedCertVerifierScopedAllowBaseSyncPrimitives;
friend class rlz_lib::FinancialPing;
friend class shell_integration_linux::
LaunchXdgUtilityScopedAllowBaseSyncPrimitives;
+ friend class storage::ObfuscatedFileUtil;
+ friend class syncer::HttpBridge;
+ friend class syncer::GetLocalChangesRequest;
+ friend class updater::SystemctlLauncherScopedAllowBaseSyncPrimitives;
+ friend class viz::ClientGpuMemoryBufferManager;
friend class webrtc::DesktopConfigurationMonitor;
- friend class content::ServiceWorkerSubresourceLoader;
- friend class blink::VideoFrameResourceProvider;
- ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF;
- ~ScopedAllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ // Usage that should be fixed:
+ // Sorted by class name (with namespace).
+ friend class ::NativeBackendKWallet; // http://crbug.com/125331
+ friend class ::ash::system::
+ StatisticsProviderImpl; // http://crbug.com/125385
+ friend class blink::VideoFrameResourceProvider; // http://crbug.com/878070
+
+ ScopedAllowBaseSyncPrimitives() DEFAULT_IF_DCHECK_IS_OFF;
+ ~ScopedAllowBaseSyncPrimitives() DEFAULT_IF_DCHECK_IS_OFF;
#if DCHECK_IS_ON()
- const bool was_disallowed_;
+ const AutoReset<BooleanWithStack> resetter_;
#endif
-
- DISALLOW_COPY_AND_ASSIGN(ScopedAllowBaseSyncPrimitives);
};
-// This can be used in a scope where blocking is disallowed.
-class BASE_EXPORT ScopedAllowBaseSyncPrimitivesOutsideBlockingScope {
+class BASE_EXPORT
+ [[maybe_unused,
+ nodiscard]] ScopedAllowBaseSyncPrimitivesOutsideBlockingScope {
+ public:
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(
+ const ScopedAllowBaseSyncPrimitivesOutsideBlockingScope&) = delete;
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope& operator=(
+ const ScopedAllowBaseSyncPrimitivesOutsideBlockingScope&) = delete;
+
private:
// This can only be instantiated by friends. Use
// ScopedAllowBaseSyncPrimitivesForTesting in unit tests to avoid the friend
@@ -358,206 +815,218 @@
FRIEND_TEST_ALL_PREFIXES(
ThreadRestrictionsTest,
ScopedAllowBaseSyncPrimitivesOutsideBlockingScopeResetsState);
+
+ // Allowed usage:
+ // Sorted by class name (with namespace).
+ friend class ::BrowserProcessImpl; // http://crbug.com/125207
friend class ::KeyStorageLinux;
+ friend class ::NativeDesktopMediaList;
+ friend class android::JavaHandlerThread;
+ friend class android_webview::
+ AwFormDatabaseService; // http://crbug.com/904431
+ friend class android_webview::CookieManager;
+ friend class android_webview::VizCompositorThreadRunnerWebView;
+ friend class audio::OutputDevice;
+ friend class base::FileDescriptorWatcher;
+ friend class base::ScopedAllowThreadRecallForStackSamplingProfiler;
+ friend class base::StackSamplingProfiler;
+ friend class base::internal::JobTaskSource;
+ friend class base::sequence_manager::internal::TaskQueueImpl;
+ friend class blink::LegacyWebRtcVideoFrameAdapter;
+ friend class blink::RTCVideoDecoderAdapter;
+ friend class blink::RTCVideoEncoder;
+ friend class blink::WebRtcVideoFrameAdapter;
+ friend class cc::CategorizedWorkerPoolImpl;
+ friend class cc::CategorizedWorkerPoolJob;
+ friend class cc::CategorizedWorkerPool;
+ friend class cc::TileTaskManagerImpl;
+ friend class content::DesktopCaptureDevice;
+ friend class content::EmergencyTraceFinalisationCoordinator;
+ friend class content::InProcessUtilityThread;
+ friend class content::RenderProcessHost;
+ friend class content::RTCVideoDecoder;
+ friend class content::SandboxHostLinux;
+ friend class content::ScopedAllowWaitForDebugURL;
friend class content::SynchronousCompositor;
friend class content::SynchronousCompositorHost;
friend class content::SynchronousCompositorSyncCallBridge;
- friend class midi::TaskService; // https://crbug.com/796830
+ friend class media::AudioInputDevice;
+ friend class media::AudioOutputDevice;
+ friend class media::PaintCanvasVideoRenderer;
+ friend class mojo::SyncCallRestrictions;
+ friend class mojo::core::ipcz_driver::MojoTrap;
+ friend class net::NetworkConfigWatcherMacThread;
+ friend class ui::DrmThreadProxy;
+ friend class viz::HostGpuMemoryBufferManager;
+ friend class vr::VrShell;
+
+ // Usage that should be fixed:
+ friend class ::chromeos::BlockingMethodCaller; // http://crbug.com/125360
+ friend class base::Thread; // http://crbug.com/918039
+ friend class cc::CompletionEvent; // http://crbug.com/902653
+ friend class content::
+ BrowserGpuChannelHostFactory; // http://crbug.com/125248
+ friend class content::TextInputClientMac; // http://crbug.com/121917
+ friend class dbus::Bus; // http://crbug.com/125222
+ friend class discardable_memory::
+ ClientDiscardableSharedMemoryManager; // http://crbug.com/1396355
+ friend class disk_cache::BackendImpl; // http://crbug.com/74623
+ friend class disk_cache::InFlightIO; // http://crbug.com/74623
+ friend class midi::TaskService; // https://crbug.com/796830
+ friend class net::
+ MultiThreadedProxyResolverScopedAllowJoinOnIO; // http://crbug.com/69710
+ friend class net::NetworkChangeNotifierMac; // http://crbug.com/125097
+ friend class net::internal::AddressTrackerLinux; // http://crbug.com/125097
+ friend class proxy_resolver::
+ ScopedAllowThreadJoinForProxyResolverV8Tracing; // http://crbug.com/69710
+ friend class remoting::AutoThread; // https://crbug.com/944316
+ friend class remoting::protocol::
+ ScopedAllowSyncPrimitivesForWebRtcDataStreamAdapter; // http://b/233844893
+ friend class remoting::protocol::
+ ScopedAllowSyncPrimitivesForWebRtcTransport; // http://crbug.com/1198501
+ friend class remoting::protocol::
+ ScopedAllowThreadJoinForWebRtcTransport; // http://crbug.com/660081
// Not used in production yet, https://crbug.com/844078.
friend class service_manager::ServiceProcessLauncher;
- friend class viz::HostGpuMemoryBufferManager;
+ friend class ui::WindowResizeHelperMac; // http://crbug.com/902829
- ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()
- EMPTY_BODY_IF_DCHECK_IS_OFF;
- ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope()
- EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ScopedAllowBaseSyncPrimitivesOutsideBlockingScope(
+ const Location& from_here = Location::Current());
+
+ ~ScopedAllowBaseSyncPrimitivesOutsideBlockingScope();
#if DCHECK_IS_ON()
- const bool was_disallowed_;
+ const AutoReset<BooleanWithStack> resetter_;
#endif
-
- DISALLOW_COPY_AND_ASSIGN(ScopedAllowBaseSyncPrimitivesOutsideBlockingScope);
};
-// This can be used in tests without being a friend of
-// ScopedAllowBaseSyncPrimitives(OutsideBlockingScope).
-class BASE_EXPORT ScopedAllowBaseSyncPrimitivesForTesting {
+// Allow base-sync-primitives in tests, doesn't require explicit friend'ing like
+// ScopedAllowBaseSyncPrimitives-types aimed at production do.
+// Note: For WaitableEvents in the test logic, base::TestWaitableEvent is
+// exposed as a convenience to avoid the need for
+// ScopedAllowBaseSyncPrimitivesForTesting.
+class BASE_EXPORT
+ [[maybe_unused, nodiscard]] ScopedAllowBaseSyncPrimitivesForTesting {
public:
- ScopedAllowBaseSyncPrimitivesForTesting() EMPTY_BODY_IF_DCHECK_IS_OFF;
- ~ScopedAllowBaseSyncPrimitivesForTesting() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ ScopedAllowBaseSyncPrimitivesForTesting() DEFAULT_IF_DCHECK_IS_OFF;
+
+ ScopedAllowBaseSyncPrimitivesForTesting(
+ const ScopedAllowBaseSyncPrimitivesForTesting&) = delete;
+ ScopedAllowBaseSyncPrimitivesForTesting& operator=(
+ const ScopedAllowBaseSyncPrimitivesForTesting&) = delete;
+
+ ~ScopedAllowBaseSyncPrimitivesForTesting() DEFAULT_IF_DCHECK_IS_OFF;
private:
#if DCHECK_IS_ON()
- const bool was_disallowed_;
+ const AutoReset<BooleanWithStack> resetter_;
#endif
+};
- DISALLOW_COPY_AND_ASSIGN(ScopedAllowBaseSyncPrimitivesForTesting);
+// Counterpart to base::DisallowUnresponsiveTasks() for tests to allow them to
+// block their thread after it was banned.
+class BASE_EXPORT
+ [[maybe_unused, nodiscard]] ScopedAllowUnresponsiveTasksForTesting {
+ public:
+ ScopedAllowUnresponsiveTasksForTesting() DEFAULT_IF_DCHECK_IS_OFF;
+
+ ScopedAllowUnresponsiveTasksForTesting(
+ const ScopedAllowUnresponsiveTasksForTesting&) = delete;
+ ScopedAllowUnresponsiveTasksForTesting& operator=(
+ const ScopedAllowUnresponsiveTasksForTesting&) = delete;
+
+ ~ScopedAllowUnresponsiveTasksForTesting() DEFAULT_IF_DCHECK_IS_OFF;
+
+ private:
+#if DCHECK_IS_ON()
+ const AutoReset<BooleanWithStack> base_sync_resetter_;
+ const AutoReset<BooleanWithStack> blocking_resetter_;
+ const AutoReset<BooleanWithStack> cpu_resetter_;
+#endif
};
namespace internal {
// Asserts that waiting on a //base sync primitive is allowed in the current
// scope.
-INLINE_IF_DCHECK_IS_OFF void AssertBaseSyncPrimitivesAllowed()
+INLINE_OR_NOT_TAIL_CALLED void AssertBaseSyncPrimitivesAllowed()
EMPTY_BODY_IF_DCHECK_IS_OFF;
// Resets all thread restrictions on the current thread.
-INLINE_IF_DCHECK_IS_OFF void ResetThreadRestrictionsForTesting()
+INLINE_OR_NOT_TAIL_CALLED void ResetThreadRestrictionsForTesting()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+// Check whether the current thread is allowed to use singletons (Singleton /
+// LazyInstance). DCHECKs if not.
+INLINE_OR_NOT_TAIL_CALLED void AssertSingletonAllowed()
EMPTY_BODY_IF_DCHECK_IS_OFF;
} // namespace internal
-// "Long CPU" work refers to any code that takes more than 100 ms to
-// run when there is no CPU contention and no hard page faults and therefore,
-// is not suitable to run on a thread required to keep the browser responsive
-// (where jank could be visible to the user).
+// Disallow using singleton on the current thread.
+INLINE_OR_NOT_TAIL_CALLED void DisallowSingleton() EMPTY_BODY_IF_DCHECK_IS_OFF;
-// Asserts that running long CPU work is allowed in the current scope.
-INLINE_IF_DCHECK_IS_OFF void AssertLongCPUWorkAllowed()
- EMPTY_BODY_IF_DCHECK_IS_OFF;
-
-// Disallows all tasks that may be unresponsive on the current thread. This
-// disallows blocking calls, waiting on a //base sync primitive and long CPU
-// work.
-INLINE_IF_DCHECK_IS_OFF void DisallowUnresponsiveTasks()
- EMPTY_BODY_IF_DCHECK_IS_OFF;
-
-class BASE_EXPORT ThreadRestrictions {
- public:
- // Constructing a ScopedAllowIO temporarily allows IO for the current
- // thread. Doing this is almost certainly always incorrect.
- //
- // DEPRECATED. Use ScopedAllowBlocking(ForTesting).
- class BASE_EXPORT ScopedAllowIO {
- public:
- ScopedAllowIO() EMPTY_BODY_IF_DCHECK_IS_OFF;
- ~ScopedAllowIO() EMPTY_BODY_IF_DCHECK_IS_OFF;
-
- private:
-#if DCHECK_IS_ON()
- const bool was_allowed_;
-#endif
-
- DISALLOW_COPY_AND_ASSIGN(ScopedAllowIO);
- };
-
-#if DCHECK_IS_ON()
- // Set whether the current thread to make IO calls.
- // Threads start out in the *allowed* state.
- // Returns the previous value.
- //
- // DEPRECATED. Use ScopedAllowBlocking(ForTesting) or ScopedDisallowBlocking.
- static bool SetIOAllowed(bool allowed);
-
- // Set whether the current thread can use singletons. Returns the previous
- // value.
- static bool SetSingletonAllowed(bool allowed);
-
- // Check whether the current thread is allowed to use singletons (Singleton /
- // LazyInstance). DCHECKs if not.
- static void AssertSingletonAllowed();
-
- // Disable waiting on the current thread. Threads start out in the *allowed*
- // state. Returns the previous value.
- //
- // DEPRECATED. Use DisallowBaseSyncPrimitives.
- static void DisallowWaiting();
#if defined(STARBOARD)
- // Get whether the current thread can use singletons.
- static bool GetSingletonAllowed();
-#endif // defined(STARBOARD)
+#if DCHECK_IS_ON()
+INLINE_OR_NOT_TAIL_CALLED bool GetSingletonAllowed() EMPTY_BODY_IF_DCHECK_IS_OFF;
#else
- // Inline the empty definitions of these functions so that they can be
- // compiled out.
- static bool SetIOAllowed(bool allowed) { return true; }
- static bool SetSingletonAllowed(bool allowed) { return true; }
- static void AssertSingletonAllowed() {}
- static void DisallowWaiting() {}
-#if defined(STARBOARD)
- // Get whether the current thread can use singletons.
- static bool GetSingletonAllowed() {return true;}
-#endif // defined(STARBOARD)
+INLINE_OR_NOT_TAIL_CALLED bool GetSingletonAllowed() { return true; }
#endif
+#endif
+
+// Disallows singletons within its scope.
+class BASE_EXPORT [[maybe_unused, nodiscard]] ScopedDisallowSingleton {
+ public:
+ ScopedDisallowSingleton() DEFAULT_IF_DCHECK_IS_OFF;
+
+ ScopedDisallowSingleton(const ScopedDisallowSingleton&) = delete;
+ ScopedDisallowSingleton& operator=(const ScopedDisallowSingleton&) = delete;
+
+ ~ScopedDisallowSingleton() DEFAULT_IF_DCHECK_IS_OFF;
private:
- // DO NOT ADD ANY OTHER FRIEND STATEMENTS.
- // BEGIN ALLOWED USAGE.
- friend class android_webview::AwFormDatabaseService;
- friend class android_webview::CookieManager;
- friend class base::StackSamplingProfiler;
- friend class content::BrowserMainLoop;
- friend class content::BrowserShutdownProfileDumper;
- friend class content::BrowserTestBase;
- friend class content::NestedMessagePumpAndroid;
- friend class content::ScopedAllowWaitForAndroidLayoutTests;
- friend class content::ScopedAllowWaitForDebugURL;
- friend class ::HistogramSynchronizer;
- friend class internal::TaskTracker;
- friend class cc::CompletionEvent;
- friend class cc::SingleThreadTaskGraphRunner;
- friend class content::CategorizedWorkerPool;
- friend class remoting::AutoThread;
- friend class ui::WindowResizeHelperMac;
- friend class MessagePumpDefault;
- friend class SimpleThread;
- friend class Thread;
- friend class ThreadTestHelper;
- friend class PlatformThread;
- friend class android::JavaHandlerThread;
- friend class mojo::SyncCallRestrictions;
- friend class ui::CommandBufferClientImpl;
- friend class ui::CommandBufferLocal;
- friend class ui::GpuState;
-
- // END ALLOWED USAGE.
- // BEGIN USAGE THAT NEEDS TO BE FIXED.
- friend class ::chromeos::BlockingMethodCaller; // http://crbug.com/125360
- friend class ::chromeos::system::StatisticsProviderImpl; // http://crbug.com/125385
- friend class chrome_browser_net::Predictor; // http://crbug.com/78451
- friend class
- content::BrowserGpuChannelHostFactory; // http://crbug.com/125248
- friend class content::TextInputClientMac; // http://crbug.com/121917
- friend class dbus::Bus; // http://crbug.com/125222
- friend class disk_cache::BackendImpl; // http://crbug.com/74623
- friend class disk_cache::InFlightIO; // http://crbug.com/74623
- friend class gpu::GpuChannelHost; // http://crbug.com/125264
- friend class net::internal::AddressTrackerLinux; // http://crbug.com/125097
- friend class net::NetworkChangeNotifierMac; // http://crbug.com/125097
- friend class ::BrowserProcessImpl; // http://crbug.com/125207
- friend class ::NativeBackendKWallet; // http://crbug.com/125331
-#if !defined(OFFICIAL_BUILD)
- friend class content::SoftwareOutputDeviceMus; // Interim non-production code
-#endif
-// END USAGE THAT NEEDS TO BE FIXED.
-
#if DCHECK_IS_ON()
- // DEPRECATED. Use ScopedAllowBaseSyncPrimitives.
- static bool SetWaitAllowed(bool allowed);
-#else
- static bool SetWaitAllowed(bool allowed) { return true; }
+ const AutoReset<BooleanWithStack> resetter_;
#endif
-
- // Constructing a ScopedAllowWait temporarily allows waiting on the current
- // thread. Doing this is almost always incorrect, which is why we limit who
- // can use this through friend.
- //
- // DEPRECATED. Use ScopedAllowBaseSyncPrimitives.
- class BASE_EXPORT ScopedAllowWait {
- public:
- ScopedAllowWait() EMPTY_BODY_IF_DCHECK_IS_OFF;
- ~ScopedAllowWait() EMPTY_BODY_IF_DCHECK_IS_OFF;
-
- private:
-#if DCHECK_IS_ON()
- const bool was_allowed_;
-#endif
-
- DISALLOW_COPY_AND_ASSIGN(ScopedAllowWait);
- };
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(ThreadRestrictions);
};
+// Asserts that running long CPU work is allowed in the current scope.
+INLINE_OR_NOT_TAIL_CALLED void AssertLongCPUWorkAllowed()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+INLINE_OR_NOT_TAIL_CALLED void DisallowUnresponsiveTasks()
+ EMPTY_BODY_IF_DCHECK_IS_OFF;
+
+// Friend-only methods to permanently allow the current thread to use
+// blocking/sync-primitives calls. Threads start out in the *allowed* state but
+// are typically *disallowed* via the above base::Disallow*() methods after
+// being initialized.
+//
+// Only use these to permanently set the allowance on a thread, e.g. on
+// shutdown. For temporary allowances, use scopers above.
+class BASE_EXPORT PermanentThreadAllowance {
+ public:
+ // Class is merely a namespace-with-friends.
+ PermanentThreadAllowance() = delete;
+
+ private:
+ // Sorted by class name (with namespace)
+ friend class base::TestCustomDisallow;
+ friend class content::BrowserMainLoop;
+ friend class content::BrowserTestBase;
+#if BUILDFLAG(IS_IOS)
+ friend class content::ContentMainRunnerImpl;
+#endif // BUILDFLAG(IS_IOS)
+ friend class web::WebMainLoop;
+
+ static void AllowBlocking() EMPTY_BODY_IF_DCHECK_IS_OFF;
+ static void AllowBaseSyncPrimitives() EMPTY_BODY_IF_DCHECK_IS_OFF;
+};
+
+#undef INLINE_OR_NOT_TAIL_CALLED
+#undef EMPTY_BODY_IF_DCHECK_IS_OFF
+#undef DEFAULT_IF_DCHECK_IS_OFF
+
} // namespace base
#endif // BASE_THREADING_THREAD_RESTRICTIONS_H_
diff --git a/base/threading/thread_restrictions_unittest.cc b/base/threading/thread_restrictions_unittest.cc
index 4b81b22..846a153 100644
--- a/base/threading/thread_restrictions_unittest.cc
+++ b/base/threading/thread_restrictions_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
+// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -6,10 +6,13 @@
#include <utility>
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/macros.h"
+#include "base/compiler_specific.h"
+#include "base/dcheck_is_on.h"
+#include "base/debug/stack_trace.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
#include "base/test/gtest_util.h"
+#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
@@ -19,47 +22,50 @@
class ThreadRestrictionsTest : public testing::Test {
public:
ThreadRestrictionsTest() = default;
+
+ ThreadRestrictionsTest(const ThreadRestrictionsTest&) = delete;
+ ThreadRestrictionsTest& operator=(const ThreadRestrictionsTest&) = delete;
+
~ThreadRestrictionsTest() override {
internal::ResetThreadRestrictionsForTesting();
}
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ThreadRestrictionsTest);
};
} // namespace
TEST_F(ThreadRestrictionsTest, BlockingAllowedByDefault) {
- AssertBlockingAllowed();
+ internal::AssertBlockingAllowed();
}
TEST_F(ThreadRestrictionsTest, ScopedDisallowBlocking) {
{
ScopedDisallowBlocking scoped_disallow_blocking;
- EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); });
+ EXPECT_DCHECK_DEATH({ internal::AssertBlockingAllowed(); });
}
- AssertBlockingAllowed();
+ internal::AssertBlockingAllowed();
}
TEST_F(ThreadRestrictionsTest, ScopedAllowBlocking) {
ScopedDisallowBlocking scoped_disallow_blocking;
{
ScopedAllowBlocking scoped_allow_blocking;
- AssertBlockingAllowed();
+ internal::AssertBlockingAllowed();
}
- EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); });
+ EXPECT_DCHECK_DEATH({ internal::AssertBlockingAllowed(); });
}
TEST_F(ThreadRestrictionsTest, ScopedAllowBlockingForTesting) {
ScopedDisallowBlocking scoped_disallow_blocking;
{
ScopedAllowBlockingForTesting scoped_allow_blocking_for_testing;
- AssertBlockingAllowed();
+ internal::AssertBlockingAllowed();
}
- EXPECT_DCHECK_DEATH({ AssertBlockingAllowed(); });
+ EXPECT_DCHECK_DEATH({ internal::AssertBlockingAllowed(); });
}
-TEST_F(ThreadRestrictionsTest, BaseSyncPrimitivesAllowedByDefault) {}
+TEST_F(ThreadRestrictionsTest, BaseSyncPrimitivesAllowedByDefault) {
+ internal::AssertBaseSyncPrimitivesAllowed();
+}
TEST_F(ThreadRestrictionsTest, DisallowBaseSyncPrimitives) {
DisallowBaseSyncPrimitives();
@@ -134,15 +140,91 @@
scoped_allow_base_sync_primitives_for_testing;
}
+TEST_F(ThreadRestrictionsTest, ScopedDisallowBaseSyncPrimitives) {
+ {
+ ScopedDisallowBaseSyncPrimitives disallow_sync_primitives;
+ EXPECT_DCHECK_DEATH({ internal::AssertBaseSyncPrimitivesAllowed(); });
+ }
+ internal::AssertBaseSyncPrimitivesAllowed();
+}
+
+TEST_F(ThreadRestrictionsTest, SingletonAllowedByDefault) {
+ internal::AssertSingletonAllowed();
+}
+
+TEST_F(ThreadRestrictionsTest, DisallowSingleton) {
+ DisallowSingleton();
+ EXPECT_DCHECK_DEATH({ internal::AssertSingletonAllowed(); });
+}
+
+TEST_F(ThreadRestrictionsTest, ScopedDisallowSingleton) {
+ {
+ ScopedDisallowSingleton disallow_sync_primitives;
+ EXPECT_DCHECK_DEATH({ internal::AssertSingletonAllowed(); });
+ }
+ internal::AssertSingletonAllowed();
+}
+
TEST_F(ThreadRestrictionsTest, LongCPUWorkAllowedByDefault) {
AssertLongCPUWorkAllowed();
}
TEST_F(ThreadRestrictionsTest, DisallowUnresponsiveTasks) {
DisallowUnresponsiveTasks();
- EXPECT_DCHECK_DEATH(AssertBlockingAllowed());
+ EXPECT_DCHECK_DEATH(internal::AssertBlockingAllowed());
EXPECT_DCHECK_DEATH(internal::AssertBaseSyncPrimitivesAllowed());
EXPECT_DCHECK_DEATH(AssertLongCPUWorkAllowed());
}
+// thread_restriction_checks_and_has_death_tests
+#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_ANDROID) && DCHECK_IS_ON() && \
+ defined(GTEST_HAS_DEATH_TEST)
+
+TEST_F(ThreadRestrictionsTest, BlockingCheckEmitsStack) {
+ ScopedDisallowBlocking scoped_disallow_blocking;
+ // The above ScopedDisallowBlocking should be on the blame list for who set
+ // the ban.
+ EXPECT_DEATH({ internal::AssertBlockingAllowed(); },
+ EXPENSIVE_DCHECKS_ARE_ON() &&
+ debug::StackTrace::WillSymbolizeToStreamForTesting()
+ ? "ScopedDisallowBlocking"
+ : "");
+ // And the stack should mention this test body as source.
+ EXPECT_DEATH({ internal::AssertBlockingAllowed(); },
+ EXPENSIVE_DCHECKS_ARE_ON() &&
+ debug::StackTrace::WillSymbolizeToStreamForTesting()
+ ? "BlockingCheckEmitsStack"
+ : "");
+}
+
+class TestCustomDisallow {
+ public:
+ NOINLINE TestCustomDisallow() { DisallowBlocking(); }
+ NOINLINE ~TestCustomDisallow() { PermanentThreadAllowance::AllowBlocking(); }
+};
+
+TEST_F(ThreadRestrictionsTest, NestedAllowRestoresPreviousStack) {
+ TestCustomDisallow custom_disallow;
+ {
+ ScopedAllowBlocking scoped_allow;
+ internal::AssertBlockingAllowed();
+ }
+ // TestCustomDisallow should be back on the blame list (as opposed to
+ // ~ScopedAllowBlocking which is the last one to have changed the state but is
+ // no longer relevant).
+ EXPECT_DEATH({ internal::AssertBlockingAllowed(); },
+ EXPENSIVE_DCHECKS_ARE_ON() &&
+ debug::StackTrace::WillSymbolizeToStreamForTesting()
+ ? "TestCustomDisallow"
+ : "");
+ // And the stack should mention this test body as source.
+ EXPECT_DEATH({ internal::AssertBlockingAllowed(); },
+ EXPENSIVE_DCHECKS_ARE_ON() &&
+ debug::StackTrace::WillSymbolizeToStreamForTesting()
+ ? "NestedAllowRestoresPreviousStack"
+ : "");
+}
+
+#endif // thread_restriction_checks_and_has_death_tests
+
} // namespace base
diff --git a/base/threading/thread_task_runner_handle.cc b/base/threading/thread_task_runner_handle.cc
index 85b47f2..b00bfae 100644
--- a/base/threading/thread_task_runner_handle.cc
+++ b/base/threading/thread_task_runner_handle.cc
@@ -1,24 +1,9 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
#include "base/threading/thread_task_runner_handle.h"
-#include <utility>
-
-#include "base/bind.h"
-#include "base/lazy_instance.h"
-#include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "base/run_loop.h"
-#include "base/threading/sequenced_task_runner_handle.h"
-#include "base/threading/thread_local.h"
-
namespace base {
-
namespace {
-base::LazyInstance<base::ThreadLocalPointer<ThreadTaskRunnerHandle>>::Leaky
+base::LazyInstance<base::ThreadLocalOwnedPointer<ThreadTaskRunnerHandle>>::Leaky
thread_task_runner_tls = LAZY_INSTANCE_INITIALIZER;
} // namespace
@@ -26,81 +11,6 @@
// static
const scoped_refptr<SingleThreadTaskRunner>& ThreadTaskRunnerHandle::Get() {
ThreadTaskRunnerHandle* current = thread_task_runner_tls.Pointer()->Get();
- CHECK(current) << "Error: This caller requires a single-threaded context "
- "(i.e. the current task needs to run from a "
- "SingleThreadTaskRunner).";
return current->task_runner_;
}
-
-// static
-bool ThreadTaskRunnerHandle::IsSet() {
- return !!thread_task_runner_tls.Pointer()->Get();
}
-
-// static
-ScopedClosureRunner ThreadTaskRunnerHandle::OverrideForTesting(
- scoped_refptr<SingleThreadTaskRunner> overriding_task_runner) {
- // OverrideForTesting() is not compatible with a SequencedTaskRunnerHandle
- // being set (but SequencedTaskRunnerHandle::IsSet() includes
- // ThreadTaskRunnerHandle::IsSet() so that's discounted as the only valid
- // excuse for it to be true). Sadly this means that tests that merely need a
- // SequencedTaskRunnerHandle on their main thread can be forced to use a
- // ThreadTaskRunnerHandle if they're also using test task runners (that
- // OverrideForTesting() when running their tasks from said main thread). To
- // solve this: sequence_task_runner_handle.cc and thread_task_runner_handle.cc
- // would have to be merged into a single impl file and share TLS state. This
- // was deemed unecessary for now as most tests should use higher level
- // constructs and not have to instantiate task runner handles on their own.
- DCHECK(!SequencedTaskRunnerHandle::IsSet() || IsSet());
-
- if (!IsSet()) {
- auto top_level_ttrh = std::make_unique<ThreadTaskRunnerHandle>(
- std::move(overriding_task_runner));
- return ScopedClosureRunner(base::BindOnce(
- [](std::unique_ptr<ThreadTaskRunnerHandle> ttrh_to_release) {},
- std::move(top_level_ttrh)));
- }
-
- ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
- // Swap the two (and below bind |overriding_task_runner|, which is now the
- // previous one, as the |task_runner_to_restore|).
- ttrh->task_runner_.swap(overriding_task_runner);
-
- auto no_running_during_override =
- std::make_unique<RunLoop::ScopedDisallowRunningForTesting>();
-
- return ScopedClosureRunner(base::BindOnce(
- [](scoped_refptr<SingleThreadTaskRunner> task_runner_to_restore,
- SingleThreadTaskRunner* expected_task_runner_before_restore,
- std::unique_ptr<RunLoop::ScopedDisallowRunningForTesting>
- no_running_during_override) {
- ThreadTaskRunnerHandle* ttrh = thread_task_runner_tls.Pointer()->Get();
-
- DCHECK_EQ(expected_task_runner_before_restore, ttrh->task_runner_.get())
- << "Nested overrides must expire their ScopedClosureRunners "
- "in LIFO order.";
-
- ttrh->task_runner_.swap(task_runner_to_restore);
- },
- std::move(overriding_task_runner),
- base::Unretained(ttrh->task_runner_.get()),
- std::move(no_running_during_override)));
-}
-
-ThreadTaskRunnerHandle::ThreadTaskRunnerHandle(
- scoped_refptr<SingleThreadTaskRunner> task_runner)
- : task_runner_(std::move(task_runner)) {
- DCHECK(task_runner_->BelongsToCurrentThread());
- // No SequencedTaskRunnerHandle (which includes ThreadTaskRunnerHandles)
- // should already be set for this thread.
- DCHECK(!SequencedTaskRunnerHandle::IsSet());
- thread_task_runner_tls.Pointer()->Set(this);
-}
-
-ThreadTaskRunnerHandle::~ThreadTaskRunnerHandle() {
- DCHECK(task_runner_->BelongsToCurrentThread());
- DCHECK_EQ(thread_task_runner_tls.Pointer()->Get(), this);
- thread_task_runner_tls.Pointer()->Set(nullptr);
-}
-
-} // namespace base
diff --git a/base/threading/thread_task_runner_handle.h b/base/threading/thread_task_runner_handle.h
index fc66fec..5d1b453 100644
--- a/base/threading/thread_task_runner_handle.h
+++ b/base/threading/thread_task_runner_handle.h
@@ -1,57 +1,20 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
#ifndef BASE_THREADING_THREAD_TASK_RUNNER_HANDLE_H_
#define BASE_THREADING_THREAD_TASK_RUNNER_HANDLE_H_
-#include "base/base_export.h"
-#include "base/callback_helpers.h"
-#include "base/compiler_specific.h"
-#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h"
+#include "base/lazy_instance.h"
+#include "base/threading/thread_local.h"
namespace base {
-// ThreadTaskRunnerHandle stores a reference to a thread's TaskRunner
-// in thread-local storage. Callers can then retrieve the TaskRunner
-// for the current thread by calling ThreadTaskRunnerHandle::Get().
-// At most one TaskRunner may be bound to each thread at a time.
-// Prefer SequencedTaskRunnerHandle to this unless thread affinity is required.
-class BASE_EXPORT ThreadTaskRunnerHandle {
+class ThreadTaskRunnerHandle {
public:
- // Gets the SingleThreadTaskRunner for the current thread.
static const scoped_refptr<SingleThreadTaskRunner>& Get();
-
- // Returns true if the SingleThreadTaskRunner is already created for
- // the current thread.
- static bool IsSet();
-
- // Overrides ThreadTaskRunnerHandle::Get()'s |task_runner_| to point at
- // |overriding_task_runner| until the returned ScopedClosureRunner goes out of
- // scope (instantiates a ThreadTaskRunnerHandle for that scope if |!IsSet()|).
- // Nested overrides are allowed but callers must ensure the
- // ScopedClosureRunners expire in LIFO (stack) order. Note: nesting
- // ThreadTaskRunnerHandles isn't generally desired but it's useful in unit
- // tests where multiple task runners can share the main thread for simplicity
- // and determinism.
- static ScopedClosureRunner OverrideForTesting(
- scoped_refptr<SingleThreadTaskRunner> overriding_task_runner)
- WARN_UNUSED_RESULT;
-
- // Binds |task_runner| to the current thread. |task_runner| must belong
- // to the current thread for this to succeed.
- explicit ThreadTaskRunnerHandle(
- scoped_refptr<SingleThreadTaskRunner> task_runner);
- ~ThreadTaskRunnerHandle();
-
private:
scoped_refptr<SingleThreadTaskRunner> task_runner_;
-
- DISALLOW_COPY_AND_ASSIGN(ThreadTaskRunnerHandle);
};
-} // namespace base
+}
-#endif // BASE_THREADING_THREAD_TASK_RUNNER_HANDLE_H_
+#endif
diff --git a/base/threading/thread_task_runner_handle_unittest.cc b/base/threading/thread_task_runner_handle_unittest.cc
deleted file mode 100644
index 1aa02d1..0000000
--- a/base/threading/thread_task_runner_handle_unittest.cc
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/threading/thread_task_runner_handle.h"
-
-#include "base/memory/ref_counted.h"
-#include "base/test/gtest_util.h"
-#include "base/test/test_simple_task_runner.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace base {
-
-TEST(ThreadTaskRunnerHandleTest, Basic) {
- scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
-
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
- {
- ThreadTaskRunnerHandle ttrh1(task_runner);
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner, ThreadTaskRunnerHandle::Get());
- }
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-}
-
-TEST(ThreadTaskRunnerHandleTest, DeathOnImplicitOverride) {
- scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
- scoped_refptr<SingleThreadTaskRunner> overidding_task_runner(
- new TestSimpleTaskRunner);
-
- ThreadTaskRunnerHandle ttrh(task_runner);
- EXPECT_DCHECK_DEATH(
- { ThreadTaskRunnerHandle overriding_ttrh(overidding_task_runner); });
-}
-
-TEST(ThreadTaskRunnerHandleTest, OverrideForTestingExistingTTRH) {
- scoped_refptr<SingleThreadTaskRunner> task_runner_1(new TestSimpleTaskRunner);
- scoped_refptr<SingleThreadTaskRunner> task_runner_2(new TestSimpleTaskRunner);
- scoped_refptr<SingleThreadTaskRunner> task_runner_3(new TestSimpleTaskRunner);
- scoped_refptr<SingleThreadTaskRunner> task_runner_4(new TestSimpleTaskRunner);
-
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
- {
- // TTRH in place prior to override.
- ThreadTaskRunnerHandle ttrh1(task_runner_1);
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
-
- {
- // Override.
- ScopedClosureRunner undo_override_2 =
- ThreadTaskRunnerHandle::OverrideForTesting(task_runner_2);
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
-
- {
- // Nested override.
- ScopedClosureRunner undo_override_3 =
- ThreadTaskRunnerHandle::OverrideForTesting(task_runner_3);
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_3, ThreadTaskRunnerHandle::Get());
- }
-
- // Back to single override.
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
-
- {
- // Backup to double override with another TTRH.
- ScopedClosureRunner undo_override_4 =
- ThreadTaskRunnerHandle::OverrideForTesting(task_runner_4);
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_4, ThreadTaskRunnerHandle::Get());
- }
- }
-
- // Back to simple TTRH.
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
- }
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-}
-
-TEST(ThreadTaskRunnerHandleTest, OverrideForTestingNoExistingTTRH) {
- scoped_refptr<SingleThreadTaskRunner> task_runner_1(new TestSimpleTaskRunner);
- scoped_refptr<SingleThreadTaskRunner> task_runner_2(new TestSimpleTaskRunner);
-
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
- {
- // Override with no TTRH in place.
- ScopedClosureRunner undo_override_1 =
- ThreadTaskRunnerHandle::OverrideForTesting(task_runner_1);
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
-
- {
- // Nested override works the same.
- ScopedClosureRunner undo_override_2 =
- ThreadTaskRunnerHandle::OverrideForTesting(task_runner_2);
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_2, ThreadTaskRunnerHandle::Get());
- }
-
- // Back to single override.
- EXPECT_TRUE(ThreadTaskRunnerHandle::IsSet());
- EXPECT_EQ(task_runner_1, ThreadTaskRunnerHandle::Get());
- }
- EXPECT_FALSE(ThreadTaskRunnerHandle::IsSet());
-}
-
-TEST(ThreadTaskRunnerHandleTest, DeathOnTTRHOverOverride) {
- scoped_refptr<SingleThreadTaskRunner> task_runner(new TestSimpleTaskRunner);
- scoped_refptr<SingleThreadTaskRunner> overidding_task_runner(
- new TestSimpleTaskRunner);
-
- ScopedClosureRunner undo_override =
- ThreadTaskRunnerHandle::OverrideForTesting(task_runner);
- EXPECT_DCHECK_DEATH(
- { ThreadTaskRunnerHandle overriding_ttrh(overidding_task_runner); });
-}
-
-} // namespace base
diff --git a/base/threading/thread_type_delegate.cc b/base/threading/thread_type_delegate.cc
new file mode 100644
index 0000000..abef61a
--- /dev/null
+++ b/base/threading/thread_type_delegate.cc
@@ -0,0 +1,13 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/threading/thread_type_delegate.h"
+
+namespace base {
+
+ThreadTypeDelegate::ThreadTypeDelegate() = default;
+
+ThreadTypeDelegate::~ThreadTypeDelegate() = default;
+
+} // namespace base
diff --git a/base/threading/thread_type_delegate.h b/base/threading/thread_type_delegate.h
new file mode 100644
index 0000000..1bbc1e0
--- /dev/null
+++ b/base/threading/thread_type_delegate.h
@@ -0,0 +1,34 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_THREAD_TYPE_DELEGATE_H_
+#define BASE_THREADING_THREAD_TYPE_DELEGATE_H_
+
+#include "base/base_export.h"
+#include "base/threading/platform_thread.h"
+
+namespace base {
+
+// A ThreadTypeDelegate can intercept thread type changes. This can be used to
+// adjust thread properties via another process when the current process can't
+// directly adjust them (e.g. due to sandbox restrictions).
+class BASE_EXPORT ThreadTypeDelegate {
+ public:
+ ThreadTypeDelegate();
+
+ ThreadTypeDelegate(const ThreadTypeDelegate&) = delete;
+ ThreadTypeDelegate& operator=(const ThreadTypeDelegate&) = delete;
+
+ virtual ~ThreadTypeDelegate();
+
+ // Invoked on thread type change. Returns true if the delegate handles
+ // adjusting thread properties (i.e. //base code will not adjust thread
+ // properties such as nice value, c-group, latency sensitivity...).
+ virtual bool HandleThreadTypeChange(PlatformThreadId thread_id,
+ ThreadType thread_type) = 0;
+};
+
+} // namespace base
+
+#endif // BASE_THREADING_THREAD_TYPE_DELEGATE_H_
diff --git a/base/threading/thread_unittest.cc b/base/threading/thread_unittest.cc
index 29ce325..dd29977 100644
--- a/base/threading/thread_unittest.cc
+++ b/base/threading/thread_unittest.cc
@@ -1,31 +1,37 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/thread.h"
+#include <stddef.h>
+#include <stdint.h>
+
#include <utility>
#include <vector>
-#include "base/bind.h"
#include "base/debug/leak_annotations.h"
-#include "base/macros.h"
+#include "base/functional/bind.h"
+#include "base/logging.h"
#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
-#include "base/message_loop/message_loop_current.h"
+#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
-#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
+#include "base/task/current_thread.h"
+#include "base/task/sequence_manager/sequence_manager_impl.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
-#include "starboard/types.h"
+#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
using base::Thread;
+using ::testing::NotNull;
typedef PlatformTest ThreadTest;
@@ -44,18 +50,20 @@
ANNOTATE_BENIGN_RACE(
this, "Benign test-only data race on vptr - http://crbug.com/98219");
}
+
+ SleepInsideInitThread(const SleepInsideInitThread&) = delete;
+ SleepInsideInitThread& operator=(const SleepInsideInitThread&) = delete;
+
~SleepInsideInitThread() override { Stop(); }
void Init() override {
- base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500));
+ base::PlatformThread::Sleep(base::Milliseconds(500));
init_called_ = true;
}
bool InitCalled() { return init_called_; }
private:
bool init_called_;
-
- DISALLOW_COPY_AND_ASSIGN(SleepInsideInitThread);
};
enum ThreadEvent {
@@ -84,6 +92,9 @@
event_list_(event_list) {
}
+ CaptureToEventList(const CaptureToEventList&) = delete;
+ CaptureToEventList& operator=(const CaptureToEventList&) = delete;
+
~CaptureToEventList() override { Stop(); }
void Init() override { event_list_->push_back(THREAD_EVENT_INIT); }
@@ -91,21 +102,23 @@
void CleanUp() override { event_list_->push_back(THREAD_EVENT_CLEANUP); }
private:
- EventList* event_list_;
-
- DISALLOW_COPY_AND_ASSIGN(CaptureToEventList);
+ raw_ptr<EventList> event_list_;
};
// Observer that writes a value into |event_list| when a message loop has been
// destroyed.
class CapturingDestructionObserver
- : public base::MessageLoopCurrent::DestructionObserver {
+ : public base::CurrentThread::DestructionObserver {
public:
// |event_list| must remain valid throughout the observer's lifetime.
explicit CapturingDestructionObserver(EventList* event_list)
: event_list_(event_list) {
}
+ CapturingDestructionObserver(const CapturingDestructionObserver&) = delete;
+ CapturingDestructionObserver& operator=(const CapturingDestructionObserver&) =
+ delete;
+
// DestructionObserver implementation:
void WillDestroyCurrentMessageLoop() override {
event_list_->push_back(THREAD_EVENT_MESSAGE_LOOP_DESTROYED);
@@ -113,15 +126,13 @@
}
private:
- EventList* event_list_;
-
- DISALLOW_COPY_AND_ASSIGN(CapturingDestructionObserver);
+ raw_ptr<EventList> event_list_;
};
// Task that adds a destruction observer to the current message loop.
void RegisterDestructionObserver(
- base::MessageLoopCurrent::DestructionObserver* observer) {
- base::MessageLoopCurrent::Get()->AddDestructionObserver(observer);
+ base::CurrentThread::DestructionObserver* observer) {
+ base::CurrentThread::Get()->AddDestructionObserver(observer);
}
// Task that calls GetThreadId() of |thread|, stores the result into |id|, then
@@ -141,16 +152,21 @@
// message. At the same time, we should scale with the bitness of the system
// where 12 kb is definitely not enough.
// 12 kb = 3072 Slots on a 32-bit system, so we'll scale based off of that.
+ int multiplier = 1;
Thread::Options options;
#if defined(ADDRESS_SANITIZER) || !defined(NDEBUG)
// ASan bloats the stack variables and overflows the 3072 slot stack. Some
// debug builds also grow the stack too much.
- options.stack_size = 2 * 3072 * sizeof(uintptr_t);
-#else
- options.stack_size = 3072 * sizeof(uintptr_t);
+ ++multiplier;
#endif
- EXPECT_TRUE(a.StartWithOptions(options));
- EXPECT_TRUE(a.message_loop());
+#if defined(LEAK_SANITIZER) && BUILDFLAG(IS_MAC)
+ // The first time an LSAN disable is fired on a thread, the LSAN Mac runtime
+ // initializes a 56k object on the stack.
+ ++multiplier;
+#endif
+ options.stack_size = 3072 * sizeof(uintptr_t) * multiplier;
+ EXPECT_TRUE(a.StartWithOptions(std::move(options)));
+ EXPECT_TRUE(a.task_runner());
EXPECT_TRUE(a.IsRunning());
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
@@ -172,8 +188,8 @@
Thread::Options options;
options.joinable = false;
- EXPECT_TRUE(a->StartWithOptions(options));
- EXPECT_TRUE(a->message_loop());
+ EXPECT_TRUE(a->StartWithOptions(std::move(options)));
+ EXPECT_TRUE(a->task_runner());
EXPECT_TRUE(a->IsRunning());
// Without this call this test is racy. The above IsRunning() succeeds because
@@ -198,7 +214,7 @@
// Unblock the task and give a bit of extra time to unwind QuitWhenIdle().
block_event.Signal();
- base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
+ base::PlatformThread::Sleep(base::Milliseconds(20));
// The thread should now have stopped on its own.
EXPECT_FALSE(a->IsRunning());
@@ -210,7 +226,7 @@
{
Thread a("TwoTasksOnJoinableThread");
EXPECT_TRUE(a.Start());
- EXPECT_TRUE(a.message_loop());
+ EXPECT_TRUE(a.task_runner());
// Test that all events are dispatched before the Thread object is
// destroyed. We do this by dispatching a sleep event before the
@@ -218,7 +234,7 @@
a.task_runner()->PostTask(
FROM_HERE, base::BindOnce(static_cast<void (*)(base::TimeDelta)>(
&base::PlatformThread::Sleep),
- base::TimeDelta::FromMilliseconds(20)));
+ base::Milliseconds(20)));
a.task_runner()->PostTask(FROM_HERE,
base::BindOnce(&ToggleValue, &was_invoked));
}
@@ -238,30 +254,30 @@
Thread a("DestroyWhileRunningNonJoinableIsSafe");
Thread::Options options;
options.joinable = false;
- EXPECT_TRUE(a.StartWithOptions(options));
+ EXPECT_TRUE(a.StartWithOptions(std::move(options)));
EXPECT_TRUE(a.WaitUntilThreadStarted());
}
// Attempt to catch use-after-frees from the non-joinable thread in the
// scope of this test if any.
- base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
+ base::PlatformThread::Sleep(base::Milliseconds(20));
}
TEST_F(ThreadTest, StopSoon) {
Thread a("StopSoon");
EXPECT_TRUE(a.Start());
- EXPECT_TRUE(a.message_loop());
+ EXPECT_TRUE(a.task_runner());
EXPECT_TRUE(a.IsRunning());
a.StopSoon();
a.Stop();
- EXPECT_FALSE(a.message_loop());
+ EXPECT_FALSE(a.task_runner());
EXPECT_FALSE(a.IsRunning());
}
TEST_F(ThreadTest, StopTwiceNop) {
Thread a("StopTwiceNop");
EXPECT_TRUE(a.Start());
- EXPECT_TRUE(a.message_loop());
+ EXPECT_TRUE(a.task_runner());
EXPECT_TRUE(a.IsRunning());
a.StopSoon();
// Calling StopSoon() a second time should be a nop.
@@ -269,7 +285,7 @@
a.Stop();
// Same with Stop().
a.Stop();
- EXPECT_FALSE(a.message_loop());
+ EXPECT_FALSE(a.task_runner());
EXPECT_FALSE(a.IsRunning());
// Calling them when not running should also nop.
a.StopSoon();
@@ -284,13 +300,15 @@
Thread b("NonOwningThread");
b.Start();
- EXPECT_DCHECK_DEATH({
- // Stopping |a| on |b| isn't allowed.
- b.task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&Thread::Stop, base::Unretained(&a)));
- // Block here so the DCHECK on |b| always happens in this scope.
- base::PlatformThread::Sleep(base::TimeDelta::Max());
- });
+ EXPECT_DCHECK_DEATH_WITH(
+ {
+ // Stopping |a| on |b| isn't allowed.
+ b.task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&Thread::Stop, base::Unretained(&a)));
+ // Block here so the DCHECK on |b| always happens in this scope.
+ base::PlatformThread::Sleep(base::TimeDelta::Max());
+ },
+ "owning_sequence_checker_.CalledOnValidSequence()");
}
TEST_F(ThreadTest, TransferOwnershipAndStop) {
@@ -322,23 +340,23 @@
TEST_F(ThreadTest, StartTwice) {
Thread a("StartTwice");
- EXPECT_FALSE(a.message_loop());
+ EXPECT_FALSE(a.task_runner());
EXPECT_FALSE(a.IsRunning());
EXPECT_TRUE(a.Start());
- EXPECT_TRUE(a.message_loop());
+ EXPECT_TRUE(a.task_runner());
EXPECT_TRUE(a.IsRunning());
a.Stop();
- EXPECT_FALSE(a.message_loop());
+ EXPECT_FALSE(a.task_runner());
EXPECT_FALSE(a.IsRunning());
EXPECT_TRUE(a.Start());
- EXPECT_TRUE(a.message_loop());
+ EXPECT_TRUE(a.task_runner());
EXPECT_TRUE(a.IsRunning());
a.Stop();
- EXPECT_FALSE(a.message_loop());
+ EXPECT_FALSE(a.task_runner());
EXPECT_FALSE(a.IsRunning());
}
@@ -354,8 +372,8 @@
Thread::Options options;
options.joinable = false;
- EXPECT_TRUE(a->StartWithOptions(options));
- EXPECT_TRUE(a->message_loop());
+ EXPECT_TRUE(a->StartWithOptions(std::move(options)));
+ EXPECT_TRUE(a->task_runner());
EXPECT_TRUE(a->IsRunning());
// Signaled when last task on |a| is processed.
@@ -372,7 +390,7 @@
a->StopSoon();
base::PlatformThread::YieldCurrentThread();
last_task_event.Wait();
- base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(20));
+ base::PlatformThread::Sleep(base::Milliseconds(20));
// This test assumes that the above was sufficient to let the thread fully
// stop.
@@ -445,7 +463,7 @@
//
// (1) Thread::CleanUp()
// (2) MessageLoop::~MessageLoop()
-// MessageLoopCurrent::DestructionObservers called.
+// CurrentThread::DestructionObservers called.
TEST_F(ThreadTest, CleanUp) {
EventList captured_events;
CapturingDestructionObserver loop_destruction_observer(&captured_events);
@@ -454,7 +472,7 @@
// Start a thread which writes its event into |captured_events|.
CaptureToEventList t(&captured_events);
EXPECT_TRUE(t.Start());
- EXPECT_TRUE(t.message_loop());
+ EXPECT_TRUE(t.task_runner());
EXPECT_TRUE(t.IsRunning());
// Register an observer that writes into |captured_events| once the
@@ -498,8 +516,7 @@
// Flushing a thread with no tasks shouldn't block.
a.FlushForTesting();
- constexpr base::TimeDelta kSleepPerTestTask =
- base::TimeDelta::FromMilliseconds(50);
+ constexpr base::TimeDelta kSleepPerTestTask = base::Milliseconds(50);
constexpr size_t kNumSleepTasks = 5;
const base::TimeTicks ticks_before_post = base::TimeTicks::Now();
@@ -523,55 +540,56 @@
namespace {
-// A Thread which uses a MessageLoop on the stack. It won't start a real
-// underlying thread (instead its messages can be processed by a RunLoop on the
-// stack).
-class ExternalMessageLoopThread : public Thread {
+using TaskQueue = base::sequence_manager::TaskQueue;
+
+class SequenceManagerThreadDelegate : public Thread::Delegate {
public:
- ExternalMessageLoopThread() : Thread("ExternalMessageLoopThread") {}
+ SequenceManagerThreadDelegate()
+ : sequence_manager_(
+ base::sequence_manager::CreateUnboundSequenceManager()),
+ task_queue_(sequence_manager_->CreateTaskQueue(
+ TaskQueue::Spec(base::sequence_manager::QueueName::DEFAULT_TQ))) {
+ sequence_manager_->SetDefaultTaskRunner(GetDefaultTaskRunner());
+ }
- ~ExternalMessageLoopThread() override { Stop(); }
+ SequenceManagerThreadDelegate(const SequenceManagerThreadDelegate&) = delete;
+ SequenceManagerThreadDelegate& operator=(
+ const SequenceManagerThreadDelegate&) = delete;
- void InstallMessageLoop() { SetMessageLoop(&external_message_loop_); }
+ ~SequenceManagerThreadDelegate() override {}
- void VerifyUsingExternalMessageLoop(
- bool expected_using_external_message_loop) {
- EXPECT_EQ(expected_using_external_message_loop,
- using_external_message_loop());
+ // Thread::Delegate:
+
+ scoped_refptr<base::SingleThreadTaskRunner> GetDefaultTaskRunner() override {
+ return task_queue_->task_runner();
+ }
+
+ void BindToCurrentThread(base::TimerSlack timer_slack) override {
+ sequence_manager_->BindToMessagePump(
+ base::MessagePump::Create(base::MessagePumpType::DEFAULT));
+ sequence_manager_->SetTimerSlack(timer_slack);
}
private:
- base::MessageLoop external_message_loop_;
-
- DISALLOW_COPY_AND_ASSIGN(ExternalMessageLoopThread);
+ std::unique_ptr<base::sequence_manager::SequenceManager> sequence_manager_;
+ scoped_refptr<TaskQueue> task_queue_;
};
} // namespace
-TEST_F(ThreadTest, ExternalMessageLoop) {
- ExternalMessageLoopThread a;
- EXPECT_FALSE(a.message_loop());
- EXPECT_FALSE(a.IsRunning());
- a.VerifyUsingExternalMessageLoop(false);
+TEST_F(ThreadTest, ProvidedThreadDelegate) {
+ Thread thread("ThreadDelegate");
+ base::Thread::Options options;
+ options.delegate = std::make_unique<SequenceManagerThreadDelegate>();
- a.InstallMessageLoop();
- EXPECT_TRUE(a.message_loop());
- EXPECT_TRUE(a.IsRunning());
- a.VerifyUsingExternalMessageLoop(true);
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner =
+ options.delegate->GetDefaultTaskRunner();
+ thread.StartWithOptions(std::move(options));
- bool ran = false;
- a.task_runner()->PostTask(
- FROM_HERE, base::BindOnce([](bool* toggled) { *toggled = true; }, &ran));
- base::RunLoop().RunUntilIdle();
- EXPECT_TRUE(ran);
+ base::WaitableEvent event;
+ task_runner->PostTask(FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal,
+ base::Unretained(&event)));
+ event.Wait();
- a.Stop();
- EXPECT_FALSE(a.message_loop());
- EXPECT_FALSE(a.IsRunning());
- a.VerifyUsingExternalMessageLoop(true);
-
- // Confirm that running any remaining tasks posted from Stop() goes smoothly
- // (e.g. https://codereview.chromium.org/2135413003/#ps300001 crashed if
- // StopSoon() posted Thread::ThreadQuitHelper() while |run_loop_| was null).
- base::RunLoop().RunUntilIdle();
+ thread.Stop();
}
diff --git a/base/threading/threading_features.h b/base/threading/threading_features.h
new file mode 100644
index 0000000..856c9ef
--- /dev/null
+++ b/base/threading/threading_features.h
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_THREADING_FEATURES_H_
+#define BASE_THREADING_THREADING_FEATURES_H_
+
+#include "base/base_export.h"
+#include "base/feature_list.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_APPLE)
+#include "base/metrics/field_trial_params.h"
+#endif
+
+namespace base {
+
+#if BUILDFLAG(IS_APPLE)
+BASE_EXPORT BASE_DECLARE_FEATURE(kOptimizedRealtimeThreadingMac);
+extern const BASE_EXPORT FeatureParam<bool>
+ kOptimizedRealtimeThreadingMacPreemptible;
+extern const BASE_EXPORT FeatureParam<double>
+ kOptimizedRealtimeThreadingMacBusy;
+extern const BASE_EXPORT FeatureParam<double>
+ kOptimizedRealtimeThreadingMacBusyLimit;
+extern const BASE_EXPORT Feature kUseThreadQoSMac;
+#endif
+
+#if BUILDFLAG(IS_WIN)
+BASE_EXPORT BASE_DECLARE_FEATURE(kAboveNormalCompositingBrowserWin);
+#endif
+
+BASE_EXPORT BASE_DECLARE_FEATURE(kEnableHangWatcher);
+
+} // namespace base
+
+#endif // BASE_THREADING_THREADING_FEATURES_H_
diff --git a/base/threading/watchdog.cc b/base/threading/watchdog.cc
index 0e48c8e..8854f2b 100644
--- a/base/threading/watchdog.cc
+++ b/base/threading/watchdog.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -41,19 +41,20 @@
// Start thread running in a Disarmed state.
Watchdog::Watchdog(const TimeDelta& duration,
const std::string& thread_watched_name,
- bool enabled)
- : enabled_(enabled),
- lock_(),
- condition_variable_(&lock_),
- state_(DISARMED),
- duration_(duration),
- thread_watched_name_(thread_watched_name),
- delegate_(this) {
- if (!enabled_)
+ bool enabled,
+ Delegate* delegate)
+ : enabled_(enabled),
+ condition_variable_(&lock_),
+ state_(DISARMED),
+ duration_(duration),
+ thread_watched_name_(thread_watched_name),
+ thread_delegate_(this),
+ delegate_(delegate) {
+ if (!enabled_) {
return; // Don't start thread, or doing anything really.
+ }
enabled_ = PlatformThread::Create(0, // Default stack size.
- &delegate_,
- &handle_);
+ &thread_delegate_, &handle_);
DCHECK(enabled_);
}
@@ -108,6 +109,14 @@
}
void Watchdog::Alarm() {
+ if (delegate_) {
+ delegate_->Alarm();
+ } else {
+ DefaultAlarm();
+ }
+}
+
+void Watchdog::DefaultAlarm() {
DVLOG(1) << "Watchdog alarmed for " << thread_watched_name_;
}
@@ -118,7 +127,7 @@
SetThreadName();
TimeDelta remaining_duration;
StaticData* static_data = GetStaticData();
- while (1) {
+ while (true) {
AutoLock lock(watchdog_->lock_);
while (DISARMED == watchdog_->state_)
watchdog_->condition_variable_.Wait();
@@ -155,7 +164,7 @@
watchdog_->Alarm(); // Set a break point here to debug on alarms.
}
TimeDelta last_alarm_delay = TimeTicks::Now() - last_alarm_time;
- if (last_alarm_delay <= TimeDelta::FromMilliseconds(2))
+ if (last_alarm_delay <= Milliseconds(2))
continue;
// Ignore race of two alarms/breaks going off at roughly the same time.
AutoLock static_lock(static_data->lock);
diff --git a/base/threading/watchdog.h b/base/threading/watchdog.h
index f806984..f09e9a2 100644
--- a/base/threading/watchdog.h
+++ b/base/threading/watchdog.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@@ -22,7 +22,7 @@
#include "base/base_export.h"
#include "base/compiler_specific.h"
-#include "base/macros.h"
+#include "base/memory/raw_ptr.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
@@ -32,11 +32,26 @@
class BASE_EXPORT Watchdog {
public:
- // Constructor specifies how long the Watchdog will wait before alarming.
+ class Delegate {
+ public:
+ virtual ~Delegate() = default;
+
+ // Called on the watchdog thread.
+ virtual void Alarm() = 0;
+ };
+
+ // Constructor specifies how long the Watchdog will wait before alarming. If
+ // `delegate` is non-null, `Alarm` on the delegate will be called instead of
+ // the default behavior.
Watchdog(const TimeDelta& duration,
const std::string& thread_watched_name,
- bool enabled);
- virtual ~Watchdog();
+ bool enabled,
+ Delegate* delegate = nullptr);
+
+ Watchdog(const Watchdog&) = delete;
+ Watchdog& operator=(const Watchdog&) = delete;
+
+ ~Watchdog();
// Notify watchdog thread to finish up. Sets the state_ to SHUTDOWN.
void Cleanup();
@@ -54,13 +69,16 @@
void Disarm();
// Alarm is called if the time expires after an Arm() without someone calling
- // Disarm(). This method can be overridden to create testable classes.
- virtual void Alarm();
+ // Disarm().
+ void Alarm();
// Reset static data to initial state. Useful for tests, to ensure
// they are independent.
static void ResetStaticData();
+ // The default behavior of Alarm() if a delegate is not provided.
+ void DefaultAlarm();
+
private:
class ThreadDelegate : public PlatformThread::Delegate {
public:
@@ -71,24 +89,23 @@
private:
void SetThreadName() const;
- Watchdog* watchdog_;
+ raw_ptr<Watchdog> watchdog_;
};
- enum State {ARMED, DISARMED, SHUTDOWN, JOINABLE };
+ enum State { ARMED, DISARMED, SHUTDOWN, JOINABLE };
bool enabled_;
- Lock lock_; // Mutex for state_.
+ Lock lock_;
ConditionVariable condition_variable_;
- State state_;
+ State state_ GUARDED_BY(lock_);
const TimeDelta duration_; // How long after start_time_ do we alarm?
const std::string thread_watched_name_;
PlatformThreadHandle handle_;
- ThreadDelegate delegate_; // Store it, because it must outlive the thread.
+ ThreadDelegate thread_delegate_; // Must outlive the thread.
+ raw_ptr<Delegate> delegate_;
TimeTicks start_time_; // Start of epoch, and alarm after duration_.
-
- DISALLOW_COPY_AND_ASSIGN(Watchdog);
};
} // namespace base
diff --git a/base/threading/watchdog_unittest.cc b/base/threading/watchdog_unittest.cc
index f534a86..e249a8c 100644
--- a/base/threading/watchdog_unittest.cc
+++ b/base/threading/watchdog_unittest.cc
@@ -1,12 +1,13 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/threading/watchdog.h"
+#include <atomic>
+
#include "base/logging.h"
-#include "base/macros.h"
-#include "base/synchronization/spin_wait.h"
+#include "base/test/spin_wait.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -18,28 +19,29 @@
//------------------------------------------------------------------------------
// Provide a derived class to facilitate testing.
-class WatchdogCounter : public Watchdog {
+class WatchdogCounter : public Watchdog::Delegate {
public:
WatchdogCounter(const TimeDelta& duration,
const std::string& thread_watched_name,
bool enabled)
- : Watchdog(duration, thread_watched_name, enabled),
- alarm_counter_(0) {
- }
+ : watchdog_(duration, thread_watched_name, enabled, this) {}
+
+ WatchdogCounter(const WatchdogCounter&) = delete;
+ WatchdogCounter& operator=(const WatchdogCounter&) = delete;
~WatchdogCounter() override = default;
void Alarm() override {
alarm_counter_++;
- Watchdog::Alarm();
+ watchdog_.DefaultAlarm();
}
- int alarm_counter() { return alarm_counter_; }
+ Watchdog& watchdog() { return watchdog_; }
+ int alarm_counter() { return alarm_counter_.load(); }
private:
- int alarm_counter_;
-
- DISALLOW_COPY_AND_ASSIGN(WatchdogCounter);
+ std::atomic<int> alarm_counter_{0};
+ Watchdog watchdog_;
};
class WatchdogTest : public testing::Test {
@@ -54,19 +56,19 @@
// Minimal constructor/destructor test.
TEST_F(WatchdogTest, StartupShutdownTest) {
- Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false);
- Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true);
+ Watchdog watchdog1(Milliseconds(300), "Disabled", false);
+ Watchdog watchdog2(Milliseconds(300), "Enabled", true);
}
// Test ability to call Arm and Disarm repeatedly.
TEST_F(WatchdogTest, ArmDisarmTest) {
- Watchdog watchdog1(TimeDelta::FromMilliseconds(300), "Disabled", false);
+ Watchdog watchdog1(Milliseconds(300), "Disabled", false);
watchdog1.Arm();
watchdog1.Disarm();
watchdog1.Arm();
watchdog1.Disarm();
- Watchdog watchdog2(TimeDelta::FromMilliseconds(300), "Enabled", true);
+ Watchdog watchdog2(Milliseconds(300), "Enabled", true);
watchdog2.Arm();
watchdog2.Disarm();
watchdog2.Arm();
@@ -75,10 +77,9 @@
// Make sure a basic alarm fires when the time has expired.
TEST_F(WatchdogTest, AlarmTest) {
- WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Enabled", true);
- watchdog.Arm();
- SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5),
- watchdog.alarm_counter() > 0);
+ WatchdogCounter watchdog(Milliseconds(10), "Enabled", true);
+ watchdog.watchdog().Arm();
+ SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(Minutes(5), watchdog.alarm_counter() > 0);
EXPECT_EQ(1, watchdog.alarm_counter());
}
@@ -86,35 +87,34 @@
TEST_F(WatchdogTest, AlarmPriorTimeTest) {
WatchdogCounter watchdog(TimeDelta(), "Enabled2", true);
// Set a time in the past.
- watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(2));
+ watchdog.watchdog().ArmSomeTimeDeltaAgo(Seconds(2));
// It should instantly go off, but certainly in less than 5 minutes.
- SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5),
- watchdog.alarm_counter() > 0);
+ SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(Minutes(5), watchdog.alarm_counter() > 0);
EXPECT_EQ(1, watchdog.alarm_counter());
}
// Make sure a disable alarm does nothing, even if we arm it.
TEST_F(WatchdogTest, ConstructorDisabledTest) {
- WatchdogCounter watchdog(TimeDelta::FromMilliseconds(10), "Disabled", false);
- watchdog.Arm();
+ WatchdogCounter watchdog(Milliseconds(10), "Disabled", false);
+ watchdog.watchdog().Arm();
// Alarm should not fire, as it was disabled.
- PlatformThread::Sleep(TimeDelta::FromMilliseconds(500));
+ PlatformThread::Sleep(Milliseconds(500));
EXPECT_EQ(0, watchdog.alarm_counter());
}
// Make sure Disarming will prevent firing, even after Arming.
TEST_F(WatchdogTest, DisarmTest) {
- WatchdogCounter watchdog(TimeDelta::FromSeconds(1), "Enabled3", true);
+ WatchdogCounter watchdog(Seconds(1), "Enabled3", true);
TimeTicks start = TimeTicks::Now();
- watchdog.Arm();
+ watchdog.watchdog().Arm();
// Sleep a bit, but not past the alarm point.
- PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
- watchdog.Disarm();
+ PlatformThread::Sleep(Milliseconds(100));
+ watchdog.watchdog().Disarm();
TimeTicks end = TimeTicks::Now();
- if (end - start > TimeDelta::FromMilliseconds(500)) {
+ if (end - start > Milliseconds(500)) {
LOG(WARNING) << "100ms sleep took over 500ms, making the results of this "
<< "timing-sensitive test suspicious. Aborting now.";
return;
@@ -125,15 +125,14 @@
// Sleep past the point where it would have fired if it wasn't disarmed,
// and verify that it didn't fire.
- PlatformThread::Sleep(TimeDelta::FromSeconds(1));
+ PlatformThread::Sleep(Seconds(1));
EXPECT_EQ(0, watchdog.alarm_counter());
// ...but even after disarming, we can still use the alarm...
// Set a time greater than the timeout into the past.
- watchdog.ArmSomeTimeDeltaAgo(TimeDelta::FromSeconds(10));
+ watchdog.watchdog().ArmSomeTimeDeltaAgo(Seconds(10));
// It should almost instantly go off, but certainly in less than 5 minutes.
- SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(TimeDelta::FromMinutes(5),
- watchdog.alarm_counter() > 0);
+ SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(Minutes(5), watchdog.alarm_counter() > 0);
EXPECT_EQ(1, watchdog.alarm_counter());
}