chromium/android_webview/browser/gfx/task_queue_webview.cc

// 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 "android_webview/browser/gfx/task_queue_webview.h"

#include <memory>
#include <utility>

#include "android_webview/common/aw_features.h"
#include "base/auto_reset.h"
#include "base/containers/queue.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.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.
//
// Lifetime: Singleton
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<base::SingleThreadTaskRunner> GetClientTaskRunner() override;
  void InitializeVizThread(const scoped_refptr<base::SingleThreadTaskRunner>&
                               viz_task_runner) override;
  void ScheduleOnVizAndBlock(VizTask viz_task) override;
  void ResetRenderThreadForTesting() override {
    DETACH_FROM_THREAD(render_thread_checker_);
  }

 private:
  void RunOnViz(VizTask viz_task);
  void SignalDone();
  void EmplaceTask(base::OnceClosure task);

  scoped_refptr<base::SingleThreadTaskRunner> 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<base::OnceClosure> 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<base::SingleThreadTaskRunner>
TaskQueueViz::GetClientTaskRunner() {
  DCHECK(viz_task_runner_);
  return viz_task_runner_;
}

void TaskQueueViz::InitializeVizThread(
    const scoped_refptr<base::SingleThreadTaskRunner>& 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_);
  SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
      "Android.WebView.ScheduleOnVizAndBlockTime");

  // 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<bool> 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<base::OnceClosure> 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<TaskQueueWebView*>(new TaskQueueViz);
  return task_queue;
}

}  // namespace android_webview