// Copyright 2019 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 "android_webview/browser/gfx/task_queue_webview.h" #include #include #include "android_webview/common/aw_features.h" #include "base/auto_reset.h" #include "base/bind.h" #include "base/containers/queue.h" #include "base/logging.h" #include "base/synchronization/condition_variable.h" #include "base/synchronization/lock.h" #include "base/thread_annotations.h" #include "base/threading/thread_checker.h" #include "base/threading/thread_local.h" #include "base/trace_event/trace_event.h" #include "components/viz/common/features.h" namespace android_webview { namespace { // The client is the single viz thread and the gpu service runs on the render // thread. Render thread is allowed to block on the viz thread, but not the // other way around. This achieves viz scheduling tasks to gpu by first blocking // render thread on the viz thread so render thread is ready to receive and run // tasks. class TaskQueueViz : public TaskQueueWebView { public: TaskQueueViz(); TaskQueueViz(const TaskQueueViz&) = delete; TaskQueueViz& operator=(const TaskQueueViz&) = delete; ~TaskQueueViz() override; // TaskQueueWebView overrides. void ScheduleTask(base::OnceClosure task, bool out_of_order) override; void ScheduleOrRetainTask(base::OnceClosure task) override; void ScheduleIdleTask(base::OnceClosure task) override; scoped_refptr GetClientTaskRunner() override; void InitializeVizThread(const scoped_refptr& viz_task_runner) override; void ScheduleOnVizAndBlock(VizTask viz_task) override; private: void RunOnViz(VizTask viz_task); void SignalDone(); void EmplaceTask(base::OnceClosure task); scoped_refptr viz_task_runner_; THREAD_CHECKER(render_thread_checker_); // Only accessed on viz thread. bool allow_schedule_task_ = false; // Only accessed on render thread. bool inside_schedule_on_viz_and_block_ = false; base::Lock lock_; base::ConditionVariable condvar_{&lock_}; bool done_ GUARDED_BY(lock_) = true; base::circular_deque tasks_ GUARDED_BY(lock_); }; TaskQueueViz::TaskQueueViz() { DETACH_FROM_THREAD(render_thread_checker_); } TaskQueueViz::~TaskQueueViz() = default; void TaskQueueViz::ScheduleTask(base::OnceClosure task, bool out_of_order) { TRACE_EVENT0("android_webview", "ScheduleTask"); DCHECK(viz_task_runner_->BelongsToCurrentThread()); DCHECK(allow_schedule_task_); // |out_of_order| is not needed by TaskForwardingSequence. Not supporting // it allows slightly more efficient swapping the task queue in // ScheduleOnVizAndBlock . DCHECK(!out_of_order); EmplaceTask(std::move(task)); } void TaskQueueViz::ScheduleOrRetainTask(base::OnceClosure task) { DCHECK(viz_task_runner_->BelongsToCurrentThread()); // The two branches end up doing the exact same thing only because retain can // use the same task queue. The code says the intention which is // |ScheduleOrRetainTask| behaves the same as |ScheduleTask| if // |allow_schedule_task_| is true. // Sharing the queue makes it clear |ScheduleTask| and |ScheduleOrRetainTask| // but however has a non-practical risk of live-locking the render thread. if (allow_schedule_task_) { ScheduleTask(std::move(task), false); return; } EmplaceTask(std::move(task)); } void TaskQueueViz::EmplaceTask(base::OnceClosure task) { base::AutoLock lock(lock_); tasks_.emplace_back(std::move(task)); condvar_.Signal(); } void TaskQueueViz::ScheduleIdleTask(base::OnceClosure task) { DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_); DCHECK(inside_schedule_on_viz_and_block_); EmplaceTask(std::move(task)); } scoped_refptr TaskQueueViz::GetClientTaskRunner() { DCHECK(viz_task_runner_); return viz_task_runner_; } void TaskQueueViz::InitializeVizThread( const scoped_refptr& viz_task_runner) { DCHECK(!viz_task_runner_); viz_task_runner_ = viz_task_runner; } void TaskQueueViz::ScheduleOnVizAndBlock(VizTask viz_task) { TRACE_EVENT0("android_webview", "ScheduleOnVizAndBlock"); DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_); // Expected behavior is |viz_task| on the viz thread. From |viz_task| until // the done closure is called (which may not be in the viz_task), viz thread // is allowed to call ScheduleTask. // // Implementation is uses a normal run-loop like logic. The done closure // marks |done_| true, and run loop exists when |done_| is true *and* the task // queue is empty. A condition variable is signaled when |done_| is set or // when something is appended to the task queue. { base::AutoLock lock(lock_); DCHECK(done_); done_ = false; } // Unretained safe because this object is never deleted. viz_task_runner_->PostTask( FROM_HERE, base::BindOnce(&TaskQueueViz::RunOnViz, base::Unretained(this), std::move(viz_task))); { DCHECK(!inside_schedule_on_viz_and_block_); base::AutoReset inside_bf(&inside_schedule_on_viz_and_block_, true); base::AutoLock lock(lock_); while (!done_ || !tasks_.empty()) { while (!done_ && tasks_.empty()) condvar_.Wait(); if (!tasks_.empty()) { base::circular_deque tasks; tasks.swap(tasks_); { base::AutoUnlock unlock(lock_); TRACE_EVENT0("android_webview", "RunTasks"); while (!tasks.empty()) { std::move(tasks.front()).Run(); tasks.pop_front(); } } } } DCHECK(done_); } } void TaskQueueViz::RunOnViz(VizTask viz_task) { DCHECK(viz_task_runner_->BelongsToCurrentThread()); DCHECK(!allow_schedule_task_); allow_schedule_task_ = true; // Unretained safe because this object is never deleted. std::move(viz_task).Run( base::BindOnce(&TaskQueueViz::SignalDone, base::Unretained(this))); } void TaskQueueViz::SignalDone() { DCHECK(viz_task_runner_->BelongsToCurrentThread()); DCHECK(allow_schedule_task_); allow_schedule_task_ = false; base::AutoLock lock(lock_); DCHECK(!done_); done_ = true; condvar_.Signal(); } } // namespace // static TaskQueueWebView* TaskQueueWebView::GetInstance() { static TaskQueueWebView* task_queue = static_cast(new TaskQueueViz); return task_queue; } } // namespace android_webview