// Copyright 2015 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 "content/renderer/devtools/devtools_cpu_throttler.h"

#if defined(OS_POSIX)
#include <signal.h>
#define USE_SIGNALS
#endif

#include "base/atomicops.h"
#include "base/macros.h"
#include "base/synchronization/cancellation_flag.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"

using base::subtle::Atomic32;
using base::subtle::Acquire_Load;
using base::subtle::Release_Store;

namespace content {

class CPUThrottlingThread final : public base::PlatformThread::Delegate {
 public:
  explicit CPUThrottlingThread(double rate);
  ~CPUThrottlingThread() override;

  void SetThrottlingRate(double rate);

 private:
  void ThreadMain() override;

  void Start();
  void Stop();
  void Throttle();

  static void SuspendThread(base::PlatformThreadHandle thread_handle);
  static void ResumeThread(base::PlatformThreadHandle thread_handle);

#ifdef USE_SIGNALS
  void InstallSignalHandler();
  void RestoreSignalHandler();
  static void HandleSignal(int signal);

  static bool signal_handler_installed_;
  static struct sigaction old_signal_handler_;
  static Atomic32 suspended_;
#endif
  static Atomic32 thread_exists_;

  base::PlatformThreadHandle throttled_thread_handle_;
  base::PlatformThreadHandle throttling_thread_handle_;
  base::CancellationFlag cancellation_flag_;
  Atomic32 throttling_rate_percent_;

  DISALLOW_COPY_AND_ASSIGN(CPUThrottlingThread);
};

#ifdef USE_SIGNALS
bool CPUThrottlingThread::signal_handler_installed_;
struct sigaction CPUThrottlingThread::old_signal_handler_;
Atomic32 CPUThrottlingThread::suspended_;
#endif
Atomic32 CPUThrottlingThread::thread_exists_;

CPUThrottlingThread::CPUThrottlingThread(double rate)
    : throttled_thread_handle_(base::PlatformThread::CurrentHandle()),
      throttling_rate_percent_(static_cast<Atomic32>(rate * 100)) {
  CHECK(base::subtle::NoBarrier_AtomicExchange(&thread_exists_, 1) == 0);
  Start();
}

CPUThrottlingThread::~CPUThrottlingThread() {
  Stop();
  CHECK(base::subtle::NoBarrier_AtomicExchange(&thread_exists_, 0) == 1);
}

void CPUThrottlingThread::SetThrottlingRate(double rate) {
  Release_Store(&throttling_rate_percent_, static_cast<Atomic32>(rate * 100));
}

void CPUThrottlingThread::ThreadMain() {
  base::PlatformThread::SetName("DevToolsCPUThrottlingThread");
  while (!cancellation_flag_.IsSet()) {
    Throttle();
  }
}

#ifdef USE_SIGNALS

// static
void CPUThrottlingThread::InstallSignalHandler() {
  // There must be the only one!
  DCHECK(!signal_handler_installed_);
  struct sigaction sa;
  sa.sa_handler = &HandleSignal;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;
  signal_handler_installed_ =
      (sigaction(SIGUSR2, &sa, &old_signal_handler_) == 0);
}

// static
void CPUThrottlingThread::RestoreSignalHandler() {
  if (!signal_handler_installed_)
    return;
  sigaction(SIGUSR2, &old_signal_handler_, 0);
  signal_handler_installed_ = false;
}

// static
void CPUThrottlingThread::HandleSignal(int signal) {
  if (signal != SIGUSR2)
    return;
  while (Acquire_Load(&suspended_)) {
  }
}

#endif  // USE_SIGNALS

// static
void CPUThrottlingThread::SuspendThread(
    base::PlatformThreadHandle thread_handle) {
#ifdef USE_SIGNALS
  Release_Store(&suspended_, 1);
  pthread_kill(thread_handle.platform_handle(), SIGUSR2);
#endif
}

// static
void CPUThrottlingThread::ResumeThread(
    base::PlatformThreadHandle thread_handle) {
#ifdef USE_SIGNALS
  Release_Store(&suspended_, 0);
#endif
}

void CPUThrottlingThread::Start() {
#ifdef USE_SIGNALS
  InstallSignalHandler();
#endif
  if (!base::PlatformThread::Create(0, this, &throttling_thread_handle_)) {
    LOG(ERROR) << "Failed to create throttling thread.";
  }
}

void CPUThrottlingThread::Stop() {
  cancellation_flag_.Set();
  base::PlatformThread::Join(throttling_thread_handle_);
#ifdef USE_SIGNALS
  RestoreSignalHandler();
#endif
}

void CPUThrottlingThread::Throttle() {
  const int quant_time_us = 200;
  double rate = Acquire_Load(&throttling_rate_percent_) / 100.;
  base::TimeDelta run_duration =
      base::TimeDelta::FromMicroseconds(static_cast<int>(quant_time_us / rate));
  base::TimeDelta sleep_duration =
      base::TimeDelta::FromMicroseconds(quant_time_us) - run_duration;
  base::PlatformThread::Sleep(run_duration);
  SuspendThread(throttled_thread_handle_);
  base::PlatformThread::Sleep(sleep_duration);
  ResumeThread(throttled_thread_handle_);
}

DevToolsCPUThrottler::DevToolsCPUThrottler() {}

DevToolsCPUThrottler::~DevToolsCPUThrottler() {}

void DevToolsCPUThrottler::SetThrottlingRate(double rate) {
  if (rate <= 1) {
    if (throttling_thread_) {
      throttling_thread_.reset();
    }
    return;
  }
  if (throttling_thread_) {
    throttling_thread_->SetThrottlingRate(rate);
  } else {
    throttling_thread_.reset(new CPUThrottlingThread(rate));
  }
}

}  // namespace content