| // 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 "content/browser/browser_thread_impl.h" |
| |
| #include <array> |
| #include <atomic> |
| #include <string> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/compiler_specific.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/no_destructor.h" |
| #include "base/notreached.h" |
| #include "base/sequence_checker.h" |
| #include "base/task/current_thread.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "content/browser/scheduler/browser_task_executor.h" |
| #include "content/public/browser/content_browser_client.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // State of a given BrowserThread::ID in chronological order throughout the |
| // browser process' lifetime. |
| enum BrowserThreadState { |
| // BrowserThread::ID isn't associated with anything yet. |
| UNINITIALIZED = 0, |
| // BrowserThread::ID is associated to a TaskRunner and is accepting tasks. |
| RUNNING, |
| // BrowserThread::ID no longer accepts tasks (it's still associated to a |
| // TaskRunner but that TaskRunner doesn't have to accept tasks). |
| SHUTDOWN |
| }; |
| |
| struct BrowserThreadGlobals { |
| BrowserThreadGlobals() { |
| // A few unit tests which do not use a BrowserTaskEnvironment still invoke |
| // code that reaches into CurrentlyOn()/IsThreadInitialized(). This can |
| // result in instantiating BrowserThreadGlobals off the main thread. |
| // |main_thread_checker_| being bound incorrectly would then result in a |
| // flake in the next test that instantiates a BrowserTaskEnvironment in the |
| // same process. Detaching here postpones binding |main_thread_checker_| to |
| // the first invocation of BrowserThreadImpl::BrowserThreadImpl() and works |
| // around this issue. |
| DETACH_FROM_THREAD(main_thread_checker_); |
| } |
| |
| // BrowserThreadGlobals must be initialized on main thread before it's used by |
| // any other threads. |
| THREAD_CHECKER(main_thread_checker_); |
| |
| // |task_runners[id]| is safe to access on |main_thread_checker_| as |
| // well as on any thread once it's read-only after initialization |
| // (i.e. while |states[id] >= RUNNING|). |
| std::array<scoped_refptr<base::SingleThreadTaskRunner>, |
| BrowserThread::ID_COUNT> |
| task_runners; |
| |
| // Tracks the runtime state of BrowserThreadImpls. Atomic because a few |
| // methods below read this value outside |main_thread_checker_| to |
| // confirm it's >= RUNNING and doing so requires an atomic read as it could be |
| // in the middle of transitioning to SHUTDOWN (which the check is fine with |
| // but reading a non-atomic value as it's written to by another thread can |
| // result in undefined behaviour on some platforms). |
| // Only NoBarrier atomic operations should be used on |states| as it shouldn't |
| // be used to establish happens-after relationships but rather checking the |
| // runtime state of various threads (once again: it's only atomic to support |
| // reading while transitioning from RUNNING=>SHUTDOWN). |
| std::array<std::atomic<BrowserThreadState>, BrowserThread::ID_COUNT> states = |
| {}; |
| }; |
| |
| BrowserThreadGlobals& GetBrowserThreadGlobals() { |
| static base::NoDestructor<BrowserThreadGlobals> globals; |
| return *globals; |
| } |
| |
| } // namespace |
| |
| scoped_refptr<base::SingleThreadTaskRunner> GetUIThreadTaskRunner( |
| const BrowserTaskTraits& traits) { |
| return BrowserTaskExecutor::GetUIThreadTaskRunner(traits); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner( |
| const BrowserTaskTraits& traits) { |
| return BrowserTaskExecutor::GetIOThreadTaskRunner(traits); |
| } |
| |
| BrowserThreadImpl::BrowserThreadImpl( |
| ID identifier, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) |
| : identifier_(identifier) { |
| DCHECK_GE(identifier_, 0); |
| DCHECK_LT(identifier_, ID_COUNT); |
| DCHECK(task_runner); |
| |
| BrowserThreadGlobals& globals = GetBrowserThreadGlobals(); |
| |
| DCHECK_CALLED_ON_VALID_THREAD(globals.main_thread_checker_); |
| |
| DCHECK_EQ( |
| UNSAFE_TODO(globals.states[identifier_]).load(std::memory_order_relaxed), |
| BrowserThreadState::UNINITIALIZED); |
| UNSAFE_TODO(globals.states[identifier_]) |
| .store(BrowserThreadState::RUNNING, std::memory_order_relaxed); |
| |
| DCHECK(UNSAFE_TODO(!globals.task_runners[identifier_])); |
| UNSAFE_TODO(globals.task_runners[identifier_]) = std::move(task_runner); |
| |
| if (identifier_ == BrowserThread::ID::UI) { |
| #if BUILDFLAG(IS_POSIX) |
| // Allow usage of the FileDescriptorWatcher API on the UI thread, using the |
| // IO thread to watch the file descriptors. |
| // |
| // In unit tests, usage of the FileDescriptorWatcher API is already allowed |
| // if the UI thread is running a MessageLoopForIO. |
| if (!base::CurrentIOThread::IsSet()) { |
| file_descriptor_watcher_.emplace(GetIOThreadTaskRunner({})); |
| } |
| base::FileDescriptorWatcher::AssertAllowed(); |
| #endif |
| } |
| } |
| |
| BrowserThreadImpl::~BrowserThreadImpl() { |
| BrowserThreadGlobals& globals = GetBrowserThreadGlobals(); |
| DCHECK_CALLED_ON_VALID_THREAD(globals.main_thread_checker_); |
| |
| DCHECK_EQ( |
| UNSAFE_TODO(globals.states[identifier_]).load(std::memory_order_relaxed), |
| BrowserThreadState::RUNNING); |
| UNSAFE_TODO(globals.states[identifier_]) |
| .store(BrowserThreadState::SHUTDOWN, std::memory_order_relaxed); |
| |
| // The mapping is kept alive after shutdown to avoid requiring a lock only for |
| // shutdown (the SingleThreadTaskRunner itself may stop accepting tasks at any |
| // point -- usually soon before/after destroying the BrowserThreadImpl). |
| DCHECK(UNSAFE_TODO(globals.task_runners[identifier_])); |
| } |
| |
| // static |
| void BrowserThreadImpl::ResetGlobalsForTesting(BrowserThread::ID identifier) { |
| BrowserThreadGlobals& globals = GetBrowserThreadGlobals(); |
| DCHECK_CALLED_ON_VALID_THREAD(globals.main_thread_checker_); |
| |
| DCHECK_EQ( |
| UNSAFE_TODO(globals.states[identifier]).load(std::memory_order_relaxed), |
| BrowserThreadState::SHUTDOWN); |
| UNSAFE_TODO(globals.states[identifier]) |
| .store(BrowserThreadState::UNINITIALIZED, std::memory_order_relaxed); |
| |
| UNSAFE_TODO(globals.task_runners[identifier]) = nullptr; |
| } |
| |
| // static |
| const char* BrowserThreadImpl::GetThreadName(BrowserThread::ID thread) { |
| static const std::array<const char* const, BrowserThread::ID_COUNT> |
| kBrowserThreadNames = { |
| "", // UI (name assembled in browser_main_loop.cc). |
| "Chrome_IOThread", // IO |
| }; |
| |
| if (BrowserThread::UI < thread && thread < BrowserThread::ID_COUNT) |
| return kBrowserThreadNames[thread]; |
| if (thread == BrowserThread::UI) |
| return "Chrome_UIThread"; |
| return "Unknown Thread"; |
| } |
| |
| // static |
| bool BrowserThread::IsThreadInitialized(ID identifier) { |
| DCHECK_GE(identifier, 0); |
| DCHECK_LT(identifier, ID_COUNT); |
| |
| BrowserThreadGlobals& globals = GetBrowserThreadGlobals(); |
| return UNSAFE_TODO(globals.states[identifier]) |
| .load(std::memory_order_relaxed) == BrowserThreadState::RUNNING; |
| } |
| |
| // static |
| bool BrowserThread::CurrentlyOn(ID identifier) { |
| DCHECK_GE(identifier, 0); |
| DCHECK_LT(identifier, ID_COUNT); |
| |
| BrowserThreadGlobals& globals = GetBrowserThreadGlobals(); |
| |
| // Thread-safe since |globals.task_runners| is read-only after being |
| // initialized from main thread (which happens before //content and embedders |
| // are kicked off and enabled to call the BrowserThread API from other |
| // threads). |
| return UNSAFE_TODO(globals.task_runners[identifier]) && |
| UNSAFE_TODO( |
| globals.task_runners[identifier]->RunsTasksInCurrentSequence()); |
| } |
| |
| // static |
| std::string BrowserThread::GetCurrentlyOnErrorMessage(ID expected) { |
| std::string actual_name = base::PlatformThread::GetName(); |
| if (actual_name.empty()) |
| actual_name = "Unknown Thread"; |
| |
| std::string result = "Must be called on "; |
| result += BrowserThreadImpl::GetThreadName(expected); |
| result += "; actually called on "; |
| result += actual_name; |
| result += "."; |
| return result; |
| } |
| |
| // static |
| bool BrowserThread::GetCurrentThreadIdentifier(ID* identifier) { |
| BrowserThreadGlobals& globals = GetBrowserThreadGlobals(); |
| |
| // Thread-safe since |globals.task_runners| is read-only after being |
| // initialized from main thread (which happens before //content and embedders |
| // are kicked off and enabled to call the BrowserThread API from other |
| // threads). |
| for (int i = 0; i < ID_COUNT; ++i) { |
| if (UNSAFE_TODO(globals.task_runners[i]) && |
| UNSAFE_TODO(globals.task_runners[i])->RunsTasksInCurrentSequence()) { |
| *identifier = static_cast<ID>(i); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // static |
| scoped_refptr<base::SingleThreadTaskRunner> |
| BrowserThread::GetTaskRunnerForThread(ID identifier) { |
| DCHECK_GE(identifier, 0); |
| DCHECK_LT(identifier, ID_COUNT); |
| switch (identifier) { |
| case UI: |
| return GetUIThreadTaskRunner({}); |
| case IO: |
| return GetIOThreadTaskRunner({}); |
| case ID_COUNT: |
| NOTREACHED(); |
| } |
| } |
| |
| // static |
| void BrowserThread::RunAllPendingTasksOnThreadForTesting(ID identifier) { |
| BrowserTaskExecutor::RunAllPendingTasksOnThreadForTesting(identifier); |
| } |
| |
| // static |
| void BrowserThread::PostBestEffortTask( |
| const base::Location& from_here, |
| scoped_refptr<base::TaskRunner> task_runner, |
| base::OnceClosure task) { |
| content::GetIOThreadTaskRunner({base::TaskPriority::BEST_EFFORT}) |
| ->PostTask( |
| FROM_HERE, |
| base::BindOnce(base::IgnoreResult(&base::TaskRunner::PostTask), |
| std::move(task_runner), from_here, std::move(task))); |
| } |
| |
| namespace internal { |
| |
| bool BrowserThreadChecker::CalledOnValidBrowserThread( |
| BrowserThread::ID thread_identifier) const { |
| return BrowserThread::CurrentlyOn(thread_identifier); |
| } |
| |
| const BrowserThreadChecker& GetBrowserThreadChecker( |
| BrowserThread::ID thread_identifier) { |
| static std::array<BrowserThreadChecker, BrowserThread::ID_COUNT> |
| browser_thread_checkers; |
| return browser_thread_checkers[thread_identifier]; |
| } |
| |
| ScopedValidateBrowserThreadChecker::ScopedValidateBrowserThreadChecker( |
| BrowserThread::ID thread_identifier, |
| base::NotFatalUntil fatal_milestone) { |
| const auto& checker = GetBrowserThreadChecker(thread_identifier); |
| CHECK(checker.CalledOnValidBrowserThread(thread_identifier), fatal_milestone) |
| << BrowserThread::GetCurrentlyOnErrorMessage(thread_identifier); |
| } |
| |
| ScopedValidateBrowserThreadChecker::~ScopedValidateBrowserThreadChecker() = |
| default; |
| |
| #if DCHECK_IS_ON() |
| ScopedValidateBrowserThreadDebugChecker:: |
| ScopedValidateBrowserThreadDebugChecker( |
| BrowserThread::ID thread_identifier) { |
| const auto& checker = GetBrowserThreadChecker(thread_identifier); |
| DCHECK(checker.CalledOnValidBrowserThread(thread_identifier)) |
| << BrowserThread::GetCurrentlyOnErrorMessage(thread_identifier); |
| } |
| #endif // DCHECK_IS_ON() |
| |
| } // namespace internal |
| |
| } // namespace content |