|  | // 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. | 
|  |  | 
|  | #ifdef UNSAFE_BUFFERS_BUILD | 
|  | // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. | 
|  | #pragma allow_unsafe_buffers | 
|  | #endif | 
|  |  | 
|  | #include "ios/web/web_thread_impl.h" | 
|  |  | 
|  | #include <string> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback.h" | 
|  | #include "base/no_destructor.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/task/sequenced_task_runner.h" | 
|  | #include "base/task/single_thread_task_runner.h" | 
|  | #include "base/time/time.h" | 
|  | #include "ios/web/public/thread/web_task_traits.h" | 
|  | #include "ios/web/public/thread/web_thread_delegate.h" | 
|  |  | 
|  | namespace web { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // State of a given WebThread::ID. | 
|  | enum WebThreadState { | 
|  | // WebThread::ID does not exist. | 
|  | UNINITIALIZED = 0, | 
|  | // WebThread::ID is associated to a TaskRunner and is accepting tasks. | 
|  | RUNNING, | 
|  | // WebThread::ID no longer accepts tasks. | 
|  | SHUTDOWN, | 
|  | }; | 
|  |  | 
|  | struct WebThreadGlobals { | 
|  | static WebThreadGlobals& Get() { | 
|  | static base::NoDestructor<WebThreadGlobals> instance; | 
|  | return *instance; | 
|  | } | 
|  |  | 
|  | static bool IsCreated() { return is_created; } | 
|  |  | 
|  | // This lock protects `threads` and `states`. Do not read or modify those | 
|  | // arrays without holding this lock. Do not block while holding this lock. | 
|  | base::Lock lock; | 
|  |  | 
|  | // This array is protected by `lock`. This array is filled as WebThreadImpls | 
|  | // are constructed and depopulated when they are destructed. | 
|  | scoped_refptr<base::SingleThreadTaskRunner> | 
|  | task_runners[WebThread::ID_COUNT] GUARDED_BY(lock); | 
|  |  | 
|  | // This array is protected by `lock`. Holds the state of each WebThread::ID. | 
|  | WebThreadState states[WebThread::ID_COUNT] GUARDED_BY(lock) = {}; | 
|  |  | 
|  | private: | 
|  | friend class base::NoDestructor<WebThreadGlobals>; | 
|  |  | 
|  | static bool is_created; | 
|  |  | 
|  | WebThreadGlobals() { is_created = true; } | 
|  | }; | 
|  |  | 
|  | bool WebThreadGlobals::is_created = false; | 
|  |  | 
|  | bool PostTaskHelper(WebThread::ID identifier, | 
|  | const base::Location& from_here, | 
|  | base::OnceClosure task, | 
|  | base::TimeDelta delay, | 
|  | bool nestable) NO_THREAD_SAFETY_ANALYSIS { | 
|  | DCHECK_GE(identifier, 0); | 
|  | DCHECK_LT(identifier, WebThread::ID_COUNT); | 
|  | // Optimization: to avoid unnecessary locks, we listed the ID enumeration in | 
|  | // order of lifetime.  So no need to lock if we know that the target thread | 
|  | // outlives current thread. | 
|  | // Note: since the array is so small, ok to loop instead of creating a map, | 
|  | // which would require a lock because std::map isn't thread safe, defeating | 
|  | // the whole purpose of this optimization. | 
|  | WebThread::ID current_thread = WebThread::ID_COUNT; | 
|  | bool target_thread_outlives_current = | 
|  | WebThread::GetCurrentThreadIdentifier(¤t_thread) && | 
|  | current_thread >= identifier; | 
|  |  | 
|  | WebThreadGlobals& globals = WebThreadGlobals::Get(); | 
|  | if (!target_thread_outlives_current) { | 
|  | globals.lock.Acquire(); | 
|  | } | 
|  |  | 
|  | const bool accepting_tasks = | 
|  | globals.states[identifier] == WebThreadState::RUNNING; | 
|  | if (accepting_tasks) { | 
|  | base::SingleThreadTaskRunner* task_runner = | 
|  | globals.task_runners[identifier].get(); | 
|  | DCHECK(task_runner); | 
|  | if (nestable) { | 
|  | task_runner->PostDelayedTask(from_here, std::move(task), delay); | 
|  | } else { | 
|  | task_runner->PostNonNestableDelayedTask(from_here, std::move(task), | 
|  | delay); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!target_thread_outlives_current) { | 
|  | globals.lock.Release(); | 
|  | } | 
|  |  | 
|  | return accepting_tasks; | 
|  | } | 
|  |  | 
|  | // An implementation of SingleThreadTaskRunner to be used in conjunction | 
|  | // with WebThread. | 
|  | class WebThreadTaskRunner : public base::SingleThreadTaskRunner { | 
|  | public: | 
|  | explicit WebThreadTaskRunner(WebThread::ID identifier) : id_(identifier) {} | 
|  |  | 
|  | WebThreadTaskRunner(const WebThreadTaskRunner&) = delete; | 
|  | WebThreadTaskRunner& operator=(const WebThreadTaskRunner&) = delete; | 
|  |  | 
|  | // SingleThreadTaskRunner implementation. | 
|  | bool PostDelayedTask(const base::Location& from_here, | 
|  | base::OnceClosure task, | 
|  | base::TimeDelta delay) override { | 
|  | return PostTaskHelper(id_, from_here, std::move(task), delay, | 
|  | true /* nestable */); | 
|  | } | 
|  |  | 
|  | bool PostNonNestableDelayedTask(const base::Location& from_here, | 
|  | base::OnceClosure task, | 
|  | base::TimeDelta delay) override { | 
|  | return PostTaskHelper(id_, from_here, std::move(task), delay, | 
|  | false /* nestable */); | 
|  | } | 
|  |  | 
|  | bool RunsTasksInCurrentSequence() const override { | 
|  | return WebThread::CurrentlyOn(id_); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | ~WebThreadTaskRunner() override {} | 
|  |  | 
|  | private: | 
|  | WebThread::ID id_; | 
|  | }; | 
|  |  | 
|  | class WebThreadTaskExecutor { | 
|  | public: | 
|  | WebThreadTaskExecutor() {} | 
|  | ~WebThreadTaskExecutor() {} | 
|  |  | 
|  | scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner( | 
|  | WebThread::ID identifier, | 
|  | const base::TaskTraits& traits) const { | 
|  | // //TODO(crbug.com/40217644): Unlike content, iOS never honored | 
|  | // `traits.priority()`... but this is where it could. | 
|  | // Ref. content::BaseBrowserTaskExecutor::GetTaskRunner() | 
|  | switch (identifier) { | 
|  | case WebThread::UI: | 
|  | return ui_thread_task_runner_; | 
|  | case WebThread::IO: | 
|  | return io_thread_task_runner_; | 
|  | case WebThread::ID_COUNT: | 
|  | NOTREACHED(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // A static getter that also verifies the instance was set and warns of steps | 
|  | // to take if not. | 
|  | static const WebThreadTaskExecutor* GetInstance() { | 
|  | DCHECK(g_instance) | 
|  | << "No web task executor created.\nHint: if this is in a unit test, " | 
|  | "you're likely missing a WebTaskEnvironment member in your " | 
|  | "fixture."; | 
|  | return g_instance; | 
|  | } | 
|  |  | 
|  | // Creates the WebThreadTaskExecutor instance. It is intentionally leaked on | 
|  | // shutdown, except in unit tests which can reset it via | 
|  | // ResetInstanceForTesting(). | 
|  | static void CreateInstance() { | 
|  | DCHECK(!g_instance); | 
|  | g_instance = new WebThreadTaskExecutor(); | 
|  | } | 
|  |  | 
|  | static void ResetInstanceForTesting() { | 
|  | DCHECK(g_instance); | 
|  | delete g_instance; | 
|  | g_instance = nullptr; | 
|  | } | 
|  |  | 
|  | private: | 
|  | static WebThreadTaskExecutor* g_instance; | 
|  |  | 
|  | scoped_refptr<WebThreadTaskRunner> ui_thread_task_runner_ = | 
|  | base::MakeRefCounted<WebThreadTaskRunner>(WebThread::UI); | 
|  | scoped_refptr<WebThreadTaskRunner> io_thread_task_runner_ = | 
|  | base::MakeRefCounted<WebThreadTaskRunner>(WebThread::IO); | 
|  | }; | 
|  |  | 
|  | // static | 
|  | WebThreadTaskExecutor* WebThreadTaskExecutor::g_instance = nullptr; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | scoped_refptr<base::SingleThreadTaskRunner> | 
|  | WebThreadImpl::GetUIThreadTaskRunner(const WebTaskTraits& traits) { | 
|  | return WebThreadTaskExecutor::GetInstance()->GetTaskRunner(WebThread::UI, | 
|  | traits); | 
|  | } | 
|  |  | 
|  | scoped_refptr<base::SingleThreadTaskRunner> | 
|  | WebThreadImpl::GetIOThreadTaskRunner(const WebTaskTraits& traits) { | 
|  | return WebThreadTaskExecutor::GetInstance()->GetTaskRunner(WebThread::IO, | 
|  | traits); | 
|  | } | 
|  |  | 
|  | WebThreadImpl::WebThreadImpl( | 
|  | ID identifier, | 
|  | scoped_refptr<base::SingleThreadTaskRunner> task_runner) | 
|  | : identifier_(identifier) { | 
|  | DCHECK(task_runner); | 
|  |  | 
|  | WebThreadGlobals& globals = WebThreadGlobals::Get(); | 
|  |  | 
|  | base::AutoLock lock(globals.lock); | 
|  | DCHECK_GE(identifier_, 0); | 
|  | DCHECK_LT(identifier_, ID_COUNT); | 
|  |  | 
|  | DCHECK_EQ(globals.states[identifier_], WebThreadState::UNINITIALIZED); | 
|  | globals.states[identifier_] = WebThreadState::RUNNING; | 
|  |  | 
|  | DCHECK(!globals.task_runners[identifier_]); | 
|  | globals.task_runners[identifier_] = std::move(task_runner); | 
|  | } | 
|  |  | 
|  | WebThreadImpl::~WebThreadImpl() { | 
|  | WebThreadGlobals& globals = WebThreadGlobals::Get(); | 
|  | base::AutoLock lock(globals.lock); | 
|  |  | 
|  | DCHECK_EQ(globals.states[identifier_], WebThreadState::RUNNING); | 
|  | globals.states[identifier_] = WebThreadState::SHUTDOWN; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void WebThreadImpl::ResetGlobalsForTesting(WebThread::ID identifier) { | 
|  | WebThreadGlobals& globals = WebThreadGlobals::Get(); | 
|  |  | 
|  | base::AutoLock lock(globals.lock); | 
|  | DCHECK_EQ(globals.states[identifier], WebThreadState::SHUTDOWN); | 
|  | globals.states[identifier] = WebThreadState::UNINITIALIZED; | 
|  | globals.task_runners[identifier] = nullptr; | 
|  | } | 
|  | // Friendly names for the well-known threads. | 
|  |  | 
|  | // static | 
|  | const char* WebThreadImpl::GetThreadName(WebThread::ID thread) { | 
|  | static const char* const kWebThreadNames[WebThread::ID_COUNT] = { | 
|  | "Web_UIThread",  // UI | 
|  | "Web_IOThread",  // IO | 
|  | }; | 
|  |  | 
|  | if (WebThread::UI <= thread && thread < WebThread::ID_COUNT) { | 
|  | return kWebThreadNames[thread]; | 
|  | } | 
|  | return "Unknown Thread"; | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool WebThreadImpl::IsThreadInitialized(ID identifier) { | 
|  | if (!WebThreadGlobals::IsCreated()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | WebThreadGlobals& globals = WebThreadGlobals::Get(); | 
|  | base::AutoLock lock(globals.lock); | 
|  | DCHECK_GE(identifier, 0); | 
|  | DCHECK_LT(identifier, ID_COUNT); | 
|  | return globals.states[identifier] == WebThreadState::RUNNING; | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool WebThreadImpl::CurrentlyOn(ID identifier) { | 
|  | WebThreadGlobals& globals = WebThreadGlobals::Get(); | 
|  | base::AutoLock lock(globals.lock); | 
|  | DCHECK_GE(identifier, 0); | 
|  | DCHECK_LT(identifier, ID_COUNT); | 
|  | return globals.task_runners[identifier] && | 
|  | globals.task_runners[identifier]->BelongsToCurrentThread(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | std::string WebThreadImpl::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 += WebThreadImpl::GetThreadName(expected); | 
|  | result += "; actually called on "; | 
|  | result += actual_name; | 
|  | result += "."; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // static | 
|  | bool WebThreadImpl::GetCurrentThreadIdentifier(ID* identifier) { | 
|  | if (!WebThreadGlobals::IsCreated()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | WebThreadGlobals& globals = WebThreadGlobals::Get(); | 
|  | base::AutoLock lock(globals.lock); | 
|  | for (int i = 0; i < ID_COUNT; ++i) { | 
|  | if (globals.task_runners[i] && | 
|  | globals.task_runners[i]->BelongsToCurrentThread()) { | 
|  | *identifier = static_cast<ID>(i); | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void WebThreadImpl::CreateTaskExecutor() { | 
|  | WebThreadTaskExecutor::CreateInstance(); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void WebThreadImpl::ResetTaskExecutorForTesting() { | 
|  | WebThreadTaskExecutor::ResetInstanceForTesting(); | 
|  | } | 
|  |  | 
|  | }  // namespace web |