1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/scheduler/public/thread_cpu_throttler.h"
#include <atomic>
#include <memory>
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/synchronization/atomic_flag.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#if BUILDFLAG(IS_POSIX)
#include <signal.h>
#define USE_SIGNALS 1
#elif BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
namespace blink {
namespace scheduler {
class ThreadCPUThrottler::ThrottlingThread final
: public base::PlatformThread::Delegate {
public:
explicit ThrottlingThread(double rate);
ThrottlingThread(const ThrottlingThread&) = delete;
ThrottlingThread& operator=(const ThrottlingThread&) = delete;
~ThrottlingThread() 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);
static void Sleep(base::TimeDelta duration);
#ifdef USE_SIGNALS
void InstallSignalHandler();
void RestoreSignalHandler();
static void HandleSignal(int signal);
static bool signal_handler_installed_;
static struct sigaction old_signal_handler_;
#endif
static std::atomic<bool> thread_exists_;
static std::atomic<int> throttling_rate_percent_;
base::PlatformThreadHandle throttled_thread_handle_;
base::PlatformThreadHandle throttling_thread_handle_;
base::AtomicFlag cancellation_flag_;
};
#ifdef USE_SIGNALS
bool ThreadCPUThrottler::ThrottlingThread::signal_handler_installed_;
struct sigaction ThreadCPUThrottler::ThrottlingThread::old_signal_handler_;
#endif
std::atomic<int> ThreadCPUThrottler::ThrottlingThread::throttling_rate_percent_;
std::atomic<bool> ThreadCPUThrottler::ThrottlingThread::thread_exists_;
ThreadCPUThrottler::ThrottlingThread::ThrottlingThread(double rate)
#ifdef OS_WIN
: throttled_thread_handle_(
::OpenThread(THREAD_SUSPEND_RESUME, false, ::GetCurrentThreadId())) {
#else
: throttled_thread_handle_(base::PlatformThread::CurrentHandle()) {
#endif
SetThrottlingRate(rate);
CHECK(!thread_exists_.exchange(true, std::memory_order_relaxed));
Start();
} // namespace scheduler
ThreadCPUThrottler::ThrottlingThread::~ThrottlingThread() {
Stop();
CHECK(thread_exists_.exchange(false, std::memory_order_relaxed));
}
void ThreadCPUThrottler::ThrottlingThread::SetThrottlingRate(double rate) {
throttling_rate_percent_.store(static_cast<int>(rate * 100),
std::memory_order_release);
}
void ThreadCPUThrottler::ThrottlingThread::ThreadMain() {
base::PlatformThread::SetName("CPUThrottlingThread");
while (!cancellation_flag_.IsSet()) {
Throttle();
}
}
#ifdef USE_SIGNALS
// static
void ThreadCPUThrottler::ThrottlingThread::InstallSignalHandler() {
// There must be the only one!
DCHECK(!signal_handler_installed_);
struct sigaction sa;
sa.sa_handler = &HandleSignal;
sigemptyset(&sa.sa_mask);
// Block SIGPROF while our handler is running so that the V8 CPU profiler
// doesn't try to sample the stack while our signal handler is active.
sigaddset(&sa.sa_mask, SIGPROF);
sa.sa_flags = SA_RESTART;
signal_handler_installed_ =
(sigaction(SIGUSR2, &sa, &old_signal_handler_) == 0);
}
// static
void ThreadCPUThrottler::ThrottlingThread::RestoreSignalHandler() {
if (!signal_handler_installed_)
return;
sigaction(SIGUSR2, &old_signal_handler_, nullptr);
signal_handler_installed_ = false;
}
// static
void ThreadCPUThrottler::ThrottlingThread::HandleSignal(int signal) {
if (signal != SIGUSR2)
return;
static base::TimeTicks lastResumeTime;
base::TimeTicks now = base::TimeTicks::Now();
base::TimeDelta run_duration = now - lastResumeTime;
uint32_t throttling_rate_percent =
throttling_rate_percent_.load(std::memory_order_acquire);
// Limit the observed run duration to 1000μs to deal with the first entrance
// to the signal handler.
uint32_t run_duration_us = static_cast<uint32_t>(
std::min(run_duration.InMicroseconds(), static_cast<int64_t>(1000)));
uint32_t sleep_duration_us =
run_duration_us * throttling_rate_percent / 100 - run_duration_us;
base::TimeTicks wake_up_time = now + base::Microseconds(sleep_duration_us);
do {
now = base::TimeTicks::Now();
} while (now < wake_up_time);
lastResumeTime = now;
}
#endif // USE_SIGNALS
void ThreadCPUThrottler::ThrottlingThread::Throttle() {
[[maybe_unused]] const int quant_time_us = 200;
#ifdef USE_SIGNALS
pthread_kill(throttled_thread_handle_.platform_handle(), SIGUSR2);
Sleep(base::Microseconds(quant_time_us));
#elif BUILDFLAG(IS_WIN)
double rate = throttling_rate_percent_.load(std::memory_order_acquire) / 100.;
base::TimeDelta run_duration =
base::Microseconds(static_cast<int>(quant_time_us / rate));
base::TimeDelta sleep_duration =
base::Microseconds(quant_time_us) - run_duration;
Sleep(run_duration);
::SuspendThread(throttled_thread_handle_.platform_handle());
Sleep(sleep_duration);
::ResumeThread(throttled_thread_handle_.platform_handle());
#endif
}
void ThreadCPUThrottler::ThrottlingThread::Start() {
#if defined(USE_SIGNALS) || BUILDFLAG(IS_WIN)
#if defined(USE_SIGNALS)
InstallSignalHandler();
#endif
if (!base::PlatformThread::Create(0, this, &throttling_thread_handle_)) {
LOG(ERROR) << "Failed to create throttling thread.";
}
#else
LOG(ERROR) << "CPU throttling is not supported.";
#endif
}
void ThreadCPUThrottler::ThrottlingThread::Sleep(base::TimeDelta duration) {
#if BUILDFLAG(IS_WIN)
// We cannot rely on ::Sleep function as it's precision is not enough for
// the purpose. Could be up to 16ms jitter.
base::TimeTicks wakeup_time = base::TimeTicks::Now() + duration;
while (base::TimeTicks::Now() < wakeup_time) {
}
#else
base::PlatformThread::Sleep(duration);
#endif
}
void ThreadCPUThrottler::ThrottlingThread::Stop() {
cancellation_flag_.Set();
base::PlatformThread::Join(throttling_thread_handle_);
#ifdef USE_SIGNALS
RestoreSignalHandler();
#endif
}
ThreadCPUThrottler::ThreadCPUThrottler() = default;
ThreadCPUThrottler::~ThreadCPUThrottler() = default;
void ThreadCPUThrottler::SetThrottlingRate(double rate) {
if (rate <= 1) {
if (throttling_thread_) {
throttling_thread_.reset();
}
return;
}
if (throttling_thread_) {
throttling_thread_->SetThrottlingRate(rate);
} else {
throttling_thread_ = std::make_unique<ThrottlingThread>(rate);
}
}
// static
ThreadCPUThrottler* ThreadCPUThrottler::GetInstance() {
return base::Singleton<ThreadCPUThrottler>::get();
}
} // namespace scheduler
} // namespace blink
|