// Copyright 2018 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 "base/tracing/perfetto_task_runner.h"

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/no_destructor.h"
#include "base/task/common/checked_lock_impl.h"
#include "base/task/common/scoped_defer_task_posting.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_local.h"
#include "base/threading/thread_local_storage.h"
#include "base/tracing/tracing_tls.h"

namespace base {
namespace tracing {

PerfettoTaskRunner::PerfettoTaskRunner(
    scoped_refptr<base::SequencedTaskRunner> task_runner)
    : task_runner_(std::move(task_runner)) {}

PerfettoTaskRunner::~PerfettoTaskRunner() {
  DCHECK(GetOrCreateTaskRunner()->RunsTasksInCurrentSequence());
#if defined(OS_POSIX) && !defined(OS_NACL)
  fd_controllers_.clear();
#endif  // defined(OS_POSIX) && !defined(OS_NACL)
}

void PerfettoTaskRunner::PostTask(std::function<void()> task) {
  PostDelayedTask(task, /* delay_ms */ 0);
}

void PerfettoTaskRunner::PostDelayedTask(std::function<void()> task,
                                         uint32_t delay_ms) {
  base::ScopedDeferTaskPosting::PostOrDefer(
      GetOrCreateTaskRunner(), FROM_HERE,
      base::BindOnce(
          [](std::function<void()> task) {
            // We block any trace events that happens while any
            // Perfetto task is running, or we'll get deadlocks in
            // situations where the StartupTraceWriterRegistry tries
            // to bind a writer which in turn causes a PostTask where
            // a trace event can be emitted, which then deadlocks as
            // it needs a new chunk from the same StartupTraceWriter
            // which we're trying to bind and are keeping the lock
            // to.
            // TODO(oysteine): Try to see if we can be more selective
            // about this.
            AutoThreadLocalBoolean thread_is_in_trace_event(
                GetThreadIsInTraceEventTLS());
            task();
          },
          task),
      base::Milliseconds(delay_ms));
}

bool PerfettoTaskRunner::RunsTasksOnCurrentThread() const {
  DCHECK(task_runner_);
  return task_runner_->RunsTasksInCurrentSequence();
}

// PlatformHandle is an int on POSIX, a HANDLE on Windows.
void PerfettoTaskRunner::AddFileDescriptorWatch(
    perfetto::libchrome::base::PlatformHandle fd,
    std::function<void()> callback) {
#if defined(OS_POSIX) && !defined(OS_NACL)
  DCHECK(GetOrCreateTaskRunner()->RunsTasksInCurrentSequence());
  DCHECK(!base::Contains(fd_controllers_, fd));
  // Set up the |fd| in the map to signal intent to add a watch. We need to
  // PostTask the WatchReadable creation because if we do it in this task we'll
  // race with perfetto setting up the connection on this task and the IO thread
  // setting up epoll on the |fd|. Using a CancelableOnceClosure ensures that
  // the |fd| won't be added for watch if RemoveFileDescriptorWatch is called.
  fd_controllers_[fd].callback.Reset(
      base::BindOnce(
          [](PerfettoTaskRunner* perfetto_runner, int fd,
             std::function<void()> callback) {
            DCHECK(perfetto_runner->GetOrCreateTaskRunner()
                       ->RunsTasksInCurrentSequence());
            // When this callback runs, we must not have removed |fd|'s watch.
            CHECK(base::Contains(perfetto_runner->fd_controllers_, fd));
            auto& controller_and_cb = perfetto_runner->fd_controllers_[fd];
            // We should never overwrite an existing watch.
            CHECK(!controller_and_cb.controller);
            controller_and_cb.controller =
                base::FileDescriptorWatcher::WatchReadable(
                    fd, base::BindRepeating(
                            [](std::function<void()> callback) { callback(); },
                            std::move(callback)));
          },
          base::Unretained(this), fd, std::move(callback)));
  task_runner_->PostTask(FROM_HERE, fd_controllers_[fd].callback.callback());
#else   // defined(OS_POSIX) && !defined(OS_NACL)
  NOTREACHED();
#endif  // defined(OS_POSIX) && !defined(OS_NACL)
}

void PerfettoTaskRunner::RemoveFileDescriptorWatch(
    perfetto::libchrome::base::PlatformHandle fd) {
#if defined(OS_POSIX) && !defined(OS_NACL)
  DCHECK(GetOrCreateTaskRunner()->RunsTasksInCurrentSequence());
  DCHECK(base::Contains(fd_controllers_, fd));
  // This also cancels the base::FileDescriptorWatcher::WatchReadable() task if
  // it's pending.
  fd_controllers_.erase(fd);
#else   // defined(OS_POSIX) && !defined(OS_NACL)
  NOTREACHED();
#endif  // defined(OS_POSIX) && !defined(OS_NACL)
}

void PerfettoTaskRunner::ResetTaskRunnerForTesting(
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  task_runner_ = std::move(task_runner);
}

void PerfettoTaskRunner::SetTaskRunner(
    scoped_refptr<base::SequencedTaskRunner> task_runner) {
  DCHECK(!task_runner_);
  task_runner_ = std::move(task_runner);
}

scoped_refptr<base::SequencedTaskRunner>
PerfettoTaskRunner::GetOrCreateTaskRunner() {
  // TODO(eseckler): This is not really thread-safe. We should probably add a
  // lock around this. At the moment we can get away without one because this
  // method is called for the first time on the process's main thread before the
  // tracing service connects.
  if (!task_runner_) {
    DCHECK(base::ThreadPoolInstance::Get());
    task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
        {base::MayBlock(), base::TaskPriority::USER_BLOCKING});
  }

  return task_runner_;
}

#if defined(OS_POSIX) && !defined(OS_NACL)
PerfettoTaskRunner::FDControllerAndCallback::FDControllerAndCallback() =
    default;

PerfettoTaskRunner::FDControllerAndCallback::~FDControllerAndCallback() =
    default;
#endif

}  // namespace tracing
}  // namespace base
