blob: 5cdd97d5499ebc61e3e38b74de2c0ffe23c0c183 [file] [log] [blame]
// Copyright 2018 the V8 project 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 "src/d8/d8-platforms.h"
#include <memory>
#include <unordered_map>
#include "include/libplatform/libplatform.h"
#include "include/v8-platform.h"
#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "src/base/platform/platform.h"
#include "src/base/platform/time.h"
#include "src/base/utils/random-number-generator.h"
namespace v8 {
class PredictablePlatform final : public Platform {
public:
explicit PredictablePlatform(std::unique_ptr<Platform> platform)
: platform_(std::move(platform)) {
DCHECK_NOT_NULL(platform_);
}
PredictablePlatform(const PredictablePlatform&) = delete;
PredictablePlatform& operator=(const PredictablePlatform&) = delete;
PageAllocator* GetPageAllocator() override {
return platform_->GetPageAllocator();
}
void OnCriticalMemoryPressure() override {
platform_->OnCriticalMemoryPressure();
}
std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate) override {
return platform_->GetForegroundTaskRunner(isolate);
}
int NumberOfWorkerThreads() override {
// The predictable platform executes everything on the main thread, but we
// still pretend to have the default number of worker threads to not
// unnecessarily change behaviour of the platform.
return platform_->NumberOfWorkerThreads();
}
void CallOnWorkerThread(std::unique_ptr<Task> task) override {
// We post worker tasks on the foreground task runner of the
// {kProcessGlobalPredictablePlatformWorkerTaskQueue} isolate. The task
// queue of the {kProcessGlobalPredictablePlatformWorkerTaskQueue} isolate
// is then executed on the main thread to achieve predictable behavior.
//
// In this context here it is okay to call {GetForegroundTaskRunner} from a
// background thread. The reason is that code is executed sequentially with
// the PredictablePlatform, and that the {DefaultPlatform} does not access
// the isolate but only uses it as the key in a HashMap.
GetForegroundTaskRunner(kProcessGlobalPredictablePlatformWorkerTaskQueue)
->PostTask(std::move(task));
}
void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
double delay_in_seconds) override {
// Never run delayed tasks.
}
bool IdleTasksEnabled(Isolate* isolate) override { return false; }
std::unique_ptr<JobHandle> PostJob(
TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
// Do not call {platform_->PostJob} here, as this would create a job that
// posts tasks directly to the underlying default platform.
std::unique_ptr<JobHandle> handle =
CreateJob(priority, std::move(job_task));
handle->NotifyConcurrencyIncrease();
return handle;
}
std::unique_ptr<JobHandle> CreateJob(
TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
// Do not call {platform_->PostJob} here, as this would create a job that
// posts tasks directly to the underlying default platform.
return platform::NewDefaultJobHandle(this, priority, std::move(job_task),
NumberOfWorkerThreads());
}
double MonotonicallyIncreasingTime() override {
// In predictable mode, there should be no (observable) concurrency, but we
// still run some tests that explicitly specify '--predictable' in the
// '--isolates' variant, where several threads run the same test in
// different isolates. To avoid TSan issues in that scenario we use atomic
// increments here.
uint64_t synthetic_time =
synthetic_time_.fetch_add(1, std::memory_order_relaxed);
return 1e-5 * synthetic_time;
}
double CurrentClockTimeMillis() override {
return MonotonicallyIncreasingTime() * base::Time::kMillisecondsPerSecond;
}
v8::TracingController* GetTracingController() override {
return platform_->GetTracingController();
}
Platform* platform() const { return platform_.get(); }
private:
std::atomic<uint64_t> synthetic_time_{0};
std::unique_ptr<Platform> platform_;
};
std::unique_ptr<Platform> MakePredictablePlatform(
std::unique_ptr<Platform> platform) {
return std::make_unique<PredictablePlatform>(std::move(platform));
}
class DelayedTasksPlatform final : public Platform {
public:
explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform)
: platform_(std::move(platform)) {
DCHECK_NOT_NULL(platform_);
}
explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform,
int64_t random_seed)
: platform_(std::move(platform)), rng_(random_seed) {
DCHECK_NOT_NULL(platform_);
}
DelayedTasksPlatform(const DelayedTasksPlatform&) = delete;
DelayedTasksPlatform& operator=(const DelayedTasksPlatform&) = delete;
~DelayedTasksPlatform() override {
// When the platform shuts down, all task runners must be freed.
DCHECK_EQ(0, delayed_task_runners_.size());
}
PageAllocator* GetPageAllocator() override {
return platform_->GetPageAllocator();
}
void OnCriticalMemoryPressure() override {
platform_->OnCriticalMemoryPressure();
}
std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
v8::Isolate* isolate) override {
std::shared_ptr<TaskRunner> runner =
platform_->GetForegroundTaskRunner(isolate);
base::MutexGuard lock_guard(&mutex_);
// Check if we can re-materialize the weak ptr in our map.
std::weak_ptr<DelayedTaskRunner>& weak_delayed_runner =
delayed_task_runners_[runner.get()];
std::shared_ptr<DelayedTaskRunner> delayed_runner =
weak_delayed_runner.lock();
if (!delayed_runner) {
// Create a new {DelayedTaskRunner} and keep a weak reference in our map.
delayed_runner.reset(new DelayedTaskRunner(runner, this),
DelayedTaskRunnerDeleter{});
weak_delayed_runner = delayed_runner;
}
return std::move(delayed_runner);
}
int NumberOfWorkerThreads() override {
return platform_->NumberOfWorkerThreads();
}
void CallOnWorkerThread(std::unique_ptr<Task> task) override {
platform_->CallOnWorkerThread(MakeDelayedTask(std::move(task)));
}
void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
double delay_in_seconds) override {
platform_->CallDelayedOnWorkerThread(MakeDelayedTask(std::move(task)),
delay_in_seconds);
}
bool IdleTasksEnabled(Isolate* isolate) override {
return platform_->IdleTasksEnabled(isolate);
}
std::unique_ptr<JobHandle> PostJob(
TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
return platform_->PostJob(priority, MakeDelayedJob(std::move(job_task)));
}
std::unique_ptr<JobHandle> CreateJob(
TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
return platform_->CreateJob(priority, MakeDelayedJob(std::move(job_task)));
}
double MonotonicallyIncreasingTime() override {
return platform_->MonotonicallyIncreasingTime();
}
double CurrentClockTimeMillis() override {
return platform_->CurrentClockTimeMillis();
}
v8::TracingController* GetTracingController() override {
return platform_->GetTracingController();
}
private:
class DelayedTaskRunnerDeleter;
class DelayedTaskRunner final : public TaskRunner {
public:
DelayedTaskRunner(std::shared_ptr<TaskRunner> task_runner,
DelayedTasksPlatform* platform)
: task_runner_(task_runner), platform_(platform) {}
void PostTask(std::unique_ptr<Task> task) final {
task_runner_->PostTask(platform_->MakeDelayedTask(std::move(task)));
}
void PostNonNestableTask(std::unique_ptr<Task> task) final {
task_runner_->PostNonNestableTask(
platform_->MakeDelayedTask(std::move(task)));
}
void PostDelayedTask(std::unique_ptr<Task> task,
double delay_in_seconds) final {
task_runner_->PostDelayedTask(platform_->MakeDelayedTask(std::move(task)),
delay_in_seconds);
}
void PostIdleTask(std::unique_ptr<IdleTask> task) final {
task_runner_->PostIdleTask(
platform_->MakeDelayedIdleTask(std::move(task)));
}
bool IdleTasksEnabled() final { return task_runner_->IdleTasksEnabled(); }
bool NonNestableTasksEnabled() const final {
return task_runner_->NonNestableTasksEnabled();
}
private:
friend class DelayedTaskRunnerDeleter;
std::shared_ptr<TaskRunner> task_runner_;
DelayedTasksPlatform* platform_;
};
class DelayedTaskRunnerDeleter {
public:
void operator()(DelayedTaskRunner* runner) const {
TaskRunner* original_runner = runner->task_runner_.get();
base::MutexGuard lock_guard(&runner->platform_->mutex_);
auto& delayed_task_runners = runner->platform_->delayed_task_runners_;
DCHECK_EQ(1, delayed_task_runners.count(original_runner));
delayed_task_runners.erase(original_runner);
}
};
class DelayedTask final : public Task {
public:
DelayedTask(std::unique_ptr<Task> task, int32_t delay_ms)
: task_(std::move(task)), delay_ms_(delay_ms) {}
void Run() override {
base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
task_->Run();
}
private:
std::unique_ptr<Task> task_;
int32_t delay_ms_;
};
class DelayedIdleTask final : public IdleTask {
public:
DelayedIdleTask(std::unique_ptr<IdleTask> task, int32_t delay_ms)
: task_(std::move(task)), delay_ms_(delay_ms) {}
void Run(double deadline_in_seconds) override {
base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
task_->Run(deadline_in_seconds);
}
private:
std::unique_ptr<IdleTask> task_;
int32_t delay_ms_;
};
class DelayedJob final : public JobTask {
public:
DelayedJob(std::unique_ptr<JobTask> job_task, int32_t delay_ms)
: job_task_(std::move(job_task)), delay_ms_(delay_ms) {}
void Run(JobDelegate* delegate) override {
// If this job is being executed via worker tasks (as e.g. the
// {DefaultJobHandle} implementation does it), the worker task would
// already include a delay. In order to not depend on that, we add our own
// delay here anyway.
base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
job_task_->Run(delegate);
}
size_t GetMaxConcurrency(size_t worker_count) const override {
return job_task_->GetMaxConcurrency(worker_count);
}
private:
std::unique_ptr<JobTask> job_task_;
int32_t delay_ms_;
};
std::unique_ptr<Platform> platform_;
// The Mutex protects the RNG, which is used by foreground and background
// threads, and the {delayed_task_runners_} map might be accessed concurrently
// by the shared_ptr destructor.
base::Mutex mutex_;
base::RandomNumberGenerator rng_;
std::unordered_map<TaskRunner*, std::weak_ptr<DelayedTaskRunner>>
delayed_task_runners_;
int32_t GetRandomDelayInMilliseconds() {
base::MutexGuard lock_guard(&mutex_);
double delay_fraction = rng_.NextDouble();
// Sleep up to 100ms (100000us). Square {delay_fraction} to shift
// distribution towards shorter sleeps.
return 1e5 * (delay_fraction * delay_fraction);
}
std::unique_ptr<Task> MakeDelayedTask(std::unique_ptr<Task> task) {
return std::make_unique<DelayedTask>(std::move(task),
GetRandomDelayInMilliseconds());
}
std::unique_ptr<IdleTask> MakeDelayedIdleTask(
std::unique_ptr<IdleTask> task) {
return std::make_unique<DelayedIdleTask>(std::move(task),
GetRandomDelayInMilliseconds());
}
std::unique_ptr<JobTask> MakeDelayedJob(std::unique_ptr<JobTask> task) {
return std::make_unique<DelayedJob>(std::move(task),
GetRandomDelayInMilliseconds());
}
};
std::unique_ptr<Platform> MakeDelayedTasksPlatform(
std::unique_ptr<Platform> platform, int64_t random_seed) {
if (random_seed) {
return std::make_unique<DelayedTasksPlatform>(std::move(platform),
random_seed);
}
return std::make_unique<DelayedTasksPlatform>(std::move(platform));
}
} // namespace v8