1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "android_webview/browser/gfx/task_queue_web_view.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "android_webview/common/aw_features.h"
11 #include "base/auto_reset.h"
12 #include "base/bind.h"
13 #include "base/containers/queue.h"
14 #include "base/logging.h"
15 #include "base/no_destructor.h"
16 #include "base/synchronization/condition_variable.h"
17 #include "base/synchronization/lock.h"
18 #include "base/thread_annotations.h"
19 #include "base/threading/thread_checker.h"
20 #include "base/threading/thread_local.h"
21 #include "base/trace_event/trace_event.h"
22 #include "components/viz/common/features.h"
23 
24 namespace android_webview {
25 
26 namespace {
27 
GetAllowGL()28 base::ThreadLocalBoolean* GetAllowGL() {
29   static base::NoDestructor<base::ThreadLocalBoolean> allow_gl;
30   return allow_gl.get();
31 }
32 
33 // This task queue is used when the client and gpu service runs on the same
34 // thread (render thread). It has some simple logic to avoid reentrancy; in most
35 // cases calling schedule will actually run the task immediately.
36 class TaskQueueSingleThread : public TaskQueueWebView {
37  public:
38   TaskQueueSingleThread();
39   ~TaskQueueSingleThread() override = default;
40 
41   // TaskQueueWebView overrides.
42   void ScheduleTask(base::OnceClosure task, bool out_of_order) override;
43   void ScheduleOrRetainTask(base::OnceClosure task) override;
44   void ScheduleIdleTask(base::OnceClosure task) override;
45   void ScheduleClientTask(base::OnceClosure task) override;
46   void RunAllTasks() override;
47   void InitializeVizThread(const scoped_refptr<base::SingleThreadTaskRunner>&
48                                viz_task_runner) override;
49   void ScheduleOnVizAndBlock(VizTask viz_task) override;
50 
51  private:
52   // Flush the idle queue until it is empty.
53   void PerformAllIdleWork();
54   void RunTasks();
55 
56   // All access to task queue should happen on a single thread.
57   THREAD_CHECKER(task_queue_thread_checker_);
58   base::circular_deque<base::OnceClosure> tasks_;
59   base::queue<base::OnceClosure> idle_tasks_;
60   base::queue<base::OnceClosure> client_tasks_;
61 
62   bool inside_run_tasks_ = false;
63   bool inside_run_idle_tasks_ = false;
64 
65   DISALLOW_COPY_AND_ASSIGN(TaskQueueSingleThread);
66 };
67 
TaskQueueSingleThread()68 TaskQueueSingleThread::TaskQueueSingleThread() {
69   DETACH_FROM_THREAD(task_queue_thread_checker_);
70 }
71 
ScheduleTask(base::OnceClosure task,bool out_of_order)72 void TaskQueueSingleThread::ScheduleTask(base::OnceClosure task,
73                                          bool out_of_order) {
74   DCHECK_CALLED_ON_VALID_THREAD(task_queue_thread_checker_);
75   LOG_IF(FATAL, !GetAllowGL()->Get())
76       << "ScheduleTask outside of ScopedAllowGL";
77   if (out_of_order)
78     tasks_.emplace_front(std::move(task));
79   else
80     tasks_.emplace_back(std::move(task));
81   RunTasks();
82 }
83 
ScheduleOrRetainTask(base::OnceClosure task)84 void TaskQueueSingleThread::ScheduleOrRetainTask(base::OnceClosure task) {
85   ScheduleTask(std::move(task), false);
86 }
87 
ScheduleIdleTask(base::OnceClosure task)88 void TaskQueueSingleThread::ScheduleIdleTask(base::OnceClosure task) {
89   LOG_IF(FATAL, !GetAllowGL()->Get())
90       << "ScheduleDelayedWork outside of ScopedAllowGL";
91   DCHECK_CALLED_ON_VALID_THREAD(task_queue_thread_checker_);
92   idle_tasks_.push(std::move(task));
93 }
94 
ScheduleClientTask(base::OnceClosure task)95 void TaskQueueSingleThread::ScheduleClientTask(base::OnceClosure task) {
96   DCHECK_CALLED_ON_VALID_THREAD(task_queue_thread_checker_);
97   client_tasks_.emplace(std::move(task));
98 }
99 
RunTasks()100 void TaskQueueSingleThread::RunTasks() {
101   TRACE_EVENT0("android_webview", "TaskQueueSingleThread::RunTasks");
102   DCHECK_CALLED_ON_VALID_THREAD(task_queue_thread_checker_);
103   if (inside_run_tasks_)
104     return;
105   base::AutoReset<bool> inside(&inside_run_tasks_, true);
106   while (tasks_.size()) {
107     std::move(tasks_.front()).Run();
108     tasks_.pop_front();
109   }
110 }
111 
RunAllTasks()112 void TaskQueueSingleThread::RunAllTasks() {
113   DCHECK_CALLED_ON_VALID_THREAD(task_queue_thread_checker_);
114   RunTasks();
115   PerformAllIdleWork();
116   DCHECK(tasks_.empty());
117   DCHECK(idle_tasks_.empty());
118 
119   // Client tasks may generate more service tasks, so run this
120   // in a loop.
121   while (!client_tasks_.empty()) {
122     base::queue<base::OnceClosure> local_client_tasks;
123     local_client_tasks.swap(client_tasks_);
124     while (!local_client_tasks.empty()) {
125       std::move(local_client_tasks.front()).Run();
126       local_client_tasks.pop();
127     }
128 
129     RunTasks();
130     PerformAllIdleWork();
131     DCHECK(tasks_.empty());
132     DCHECK(idle_tasks_.empty());
133   }
134 }
135 
PerformAllIdleWork()136 void TaskQueueSingleThread::PerformAllIdleWork() {
137   TRACE_EVENT0("android_webview", "TaskQueueWebview::PerformAllIdleWork");
138   DCHECK_CALLED_ON_VALID_THREAD(task_queue_thread_checker_);
139   if (inside_run_idle_tasks_)
140     return;
141   base::AutoReset<bool> inside(&inside_run_idle_tasks_, true);
142   while (idle_tasks_.size() > 0) {
143     base::OnceClosure task = std::move(idle_tasks_.front());
144     idle_tasks_.pop();
145     std::move(task).Run();
146   }
147 }
148 
InitializeVizThread(const scoped_refptr<base::SingleThreadTaskRunner> & viz_task_runner)149 void TaskQueueSingleThread::InitializeVizThread(
150     const scoped_refptr<base::SingleThreadTaskRunner>& viz_task_runner) {
151   NOTREACHED();
152 }
153 
ScheduleOnVizAndBlock(VizTask viz_task)154 void TaskQueueSingleThread::ScheduleOnVizAndBlock(VizTask viz_task) {
155   NOTREACHED();
156 }
157 
158 // This class is used with kVizForWebView. The client is the single viz
159 // thread and the gpu service runs on the render thread. Render thread is
160 // allowed to block on the viz thread, but not the other way around. This
161 // achieves viz scheduling tasks to gpu by first blocking render thread
162 // on the viz thread so render thread is ready to receive and run tasks.
163 //
164 // This class does not implement methods only needed by command buffer.
165 // It does not reply on ScopedAllowGL either.
166 class TaskQueueViz : public TaskQueueWebView {
167  public:
168   TaskQueueViz();
169   ~TaskQueueViz() override;
170 
171   // TaskQueueWebView overrides.
172   void ScheduleTask(base::OnceClosure task, bool out_of_order) override;
173   void ScheduleOrRetainTask(base::OnceClosure task) override;
174   void ScheduleIdleTask(base::OnceClosure task) override;
175   void ScheduleClientTask(base::OnceClosure task) override;
176   void RunAllTasks() override;
177   void InitializeVizThread(const scoped_refptr<base::SingleThreadTaskRunner>&
178                                viz_task_runner) override;
179   void ScheduleOnVizAndBlock(VizTask viz_task) override;
180 
181  private:
182   void RunOnViz(VizTask viz_task);
183   void SignalDone();
184   void EmplaceTask(base::OnceClosure task);
185 
186   scoped_refptr<base::SingleThreadTaskRunner> viz_task_runner_;
187   THREAD_CHECKER(render_thread_checker_);
188 
189   // Only accessed on viz thread.
190   bool allow_schedule_task_ = false;
191 
192   // Only accessed on render thread.
193   bool inside_schedule_on_viz_and_block_ = false;
194 
195   base::Lock lock_;
196   base::ConditionVariable condvar_{&lock_};
197   bool done_ GUARDED_BY(lock_) = true;
198   base::circular_deque<base::OnceClosure> tasks_ GUARDED_BY(lock_);
199 
200   DISALLOW_COPY_AND_ASSIGN(TaskQueueViz);
201 };
202 
TaskQueueViz()203 TaskQueueViz::TaskQueueViz() {
204   DETACH_FROM_THREAD(render_thread_checker_);
205 }
206 
207 TaskQueueViz::~TaskQueueViz() = default;
208 
ScheduleTask(base::OnceClosure task,bool out_of_order)209 void TaskQueueViz::ScheduleTask(base::OnceClosure task, bool out_of_order) {
210   TRACE_EVENT0("android_webview", "ScheduleTask");
211   DCHECK(viz_task_runner_->BelongsToCurrentThread());
212   DCHECK(allow_schedule_task_);
213   // |out_of_order| is not needed by TaskForwardingSequence. Not supporting
214   // it allows slightly more efficient swapping the task queue in
215   // ScheduleOnVizAndBlock .
216   DCHECK(!out_of_order);
217   EmplaceTask(std::move(task));
218 }
219 
ScheduleOrRetainTask(base::OnceClosure task)220 void TaskQueueViz::ScheduleOrRetainTask(base::OnceClosure task) {
221   DCHECK(viz_task_runner_->BelongsToCurrentThread());
222   // The two branches end up doing the exact same thing only because retain can
223   // use the same task queue. The code says the intention which is
224   // |ScheduleOrRetainTask| behaves the same as |ScheduleTask| if
225   // |allow_schedule_task_| is true.
226   // Sharing the queue makes it clear |ScheduleTask| and |ScheduleOrRetainTask|
227   // but however has a non-practical risk of live-locking the render thread.
228   if (allow_schedule_task_) {
229     ScheduleTask(std::move(task), false);
230     return;
231   }
232   EmplaceTask(std::move(task));
233 }
234 
EmplaceTask(base::OnceClosure task)235 void TaskQueueViz::EmplaceTask(base::OnceClosure task) {
236   base::AutoLock lock(lock_);
237   tasks_.emplace_back(std::move(task));
238   condvar_.Signal();
239 }
240 
ScheduleIdleTask(base::OnceClosure task)241 void TaskQueueViz::ScheduleIdleTask(base::OnceClosure task) {
242   DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
243   DCHECK(inside_schedule_on_viz_and_block_);
244   EmplaceTask(std::move(task));
245 }
246 
ScheduleClientTask(base::OnceClosure task)247 void TaskQueueViz::ScheduleClientTask(base::OnceClosure task) {
248   DCHECK(viz_task_runner_);
249   viz_task_runner_->PostTask(FROM_HERE, std::move(task));
250 }
251 
RunAllTasks()252 void TaskQueueViz::RunAllTasks() {
253   // Intentional no-op.
254 }
255 
InitializeVizThread(const scoped_refptr<base::SingleThreadTaskRunner> & viz_task_runner)256 void TaskQueueViz::InitializeVizThread(
257     const scoped_refptr<base::SingleThreadTaskRunner>& viz_task_runner) {
258   DCHECK(!viz_task_runner_);
259   viz_task_runner_ = viz_task_runner;
260 }
261 
ScheduleOnVizAndBlock(VizTask viz_task)262 void TaskQueueViz::ScheduleOnVizAndBlock(VizTask viz_task) {
263   TRACE_EVENT0("android_webview", "ScheduleOnVizAndBlock");
264   DCHECK_CALLED_ON_VALID_THREAD(render_thread_checker_);
265 
266   // Expected behavior is |viz_task| on the viz thread. From |viz_task| until
267   // the done closure is called (which may not be in the viz_task), viz thread
268   // is allowed to call ScheduleTask.
269   //
270   // Implementation is uses a normal run-loop like logic. The done closure
271   // marks |done_| true, and run loop exists when |done_| is true *and* the task
272   // queue is empty. A condition variable is signaled when |done_| is set or
273   // when something is appended to the task queue.
274   {
275     base::AutoLock lock(lock_);
276     DCHECK(done_);
277     done_ = false;
278   }
279 
280   // Unretained safe because this object is never deleted.
281   viz_task_runner_->PostTask(
282       FROM_HERE, base::BindOnce(&TaskQueueViz::RunOnViz, base::Unretained(this),
283                                 std::move(viz_task)));
284 
285   {
286     DCHECK(!inside_schedule_on_viz_and_block_);
287     base::AutoReset<bool> inside_bf(&inside_schedule_on_viz_and_block_, true);
288 
289     base::AutoLock lock(lock_);
290     while (!done_ || !tasks_.empty()) {
291       while (!done_ && tasks_.empty())
292         condvar_.Wait();
293       if (!tasks_.empty()) {
294         base::circular_deque<base::OnceClosure> tasks;
295         tasks.swap(tasks_);
296         {
297           base::AutoUnlock unlock(lock_);
298           TRACE_EVENT0("android_webview", "RunTasks");
299           while (!tasks.empty()) {
300             std::move(tasks.front()).Run();
301             tasks.pop_front();
302           }
303         }
304       }
305     }
306     DCHECK(done_);
307   }
308 }
309 
RunOnViz(VizTask viz_task)310 void TaskQueueViz::RunOnViz(VizTask viz_task) {
311   DCHECK(viz_task_runner_->BelongsToCurrentThread());
312   DCHECK(!allow_schedule_task_);
313   allow_schedule_task_ = true;
314   // Unretained safe because this object is never deleted.
315   std::move(viz_task).Run(
316       base::BindOnce(&TaskQueueViz::SignalDone, base::Unretained(this)));
317 }
318 
SignalDone()319 void TaskQueueViz::SignalDone() {
320   DCHECK(viz_task_runner_->BelongsToCurrentThread());
321   DCHECK(allow_schedule_task_);
322   allow_schedule_task_ = false;
323 
324   base::AutoLock lock(lock_);
325   DCHECK(!done_);
326   done_ = true;
327   condvar_.Signal();
328 }
329 
330 }  // namespace
331 
ScopedAllowGL()332 ScopedAllowGL::ScopedAllowGL() {
333   DCHECK(!GetAllowGL()->Get());
334   GetAllowGL()->Set(true);
335 }
336 
~ScopedAllowGL()337 ScopedAllowGL::~ScopedAllowGL() {
338   TaskQueueWebView* service = TaskQueueWebView::GetInstance();
339   DCHECK(service);
340   service->RunAllTasks();
341   GetAllowGL()->Set(false);
342 }
343 
344 // static
GetInstance()345 TaskQueueWebView* TaskQueueWebView::GetInstance() {
346   static TaskQueueWebView* task_queue =
347       ::features::IsUsingVizForWebView()
348           ? static_cast<TaskQueueWebView*>(new TaskQueueViz)
349           : static_cast<TaskQueueWebView*>(new TaskQueueSingleThread);
350   return task_queue;
351 }
352 
353 }  // namespace android_webview
354