| // 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 |