1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/d8/d8-platforms.h"
6
7 #include <memory>
8 #include <unordered_map>
9
10 #include "include/libplatform/libplatform.h"
11 #include "include/v8-platform.h"
12 #include "src/base/logging.h"
13 #include "src/base/macros.h"
14 #include "src/base/platform/mutex.h"
15 #include "src/base/platform/platform.h"
16 #include "src/base/platform/time.h"
17 #include "src/base/utils/random-number-generator.h"
18
19 namespace v8 {
20
21 class PredictablePlatform final : public Platform {
22 public:
PredictablePlatform(std::unique_ptr<Platform> platform)23 explicit PredictablePlatform(std::unique_ptr<Platform> platform)
24 : platform_(std::move(platform)) {
25 DCHECK_NOT_NULL(platform_);
26 }
27
28 PredictablePlatform(const PredictablePlatform&) = delete;
29 PredictablePlatform& operator=(const PredictablePlatform&) = delete;
30
GetPageAllocator()31 PageAllocator* GetPageAllocator() override {
32 return platform_->GetPageAllocator();
33 }
34
OnCriticalMemoryPressure()35 void OnCriticalMemoryPressure() override {
36 platform_->OnCriticalMemoryPressure();
37 }
38
OnCriticalMemoryPressure(size_t length)39 bool OnCriticalMemoryPressure(size_t length) override {
40 return platform_->OnCriticalMemoryPressure(length);
41 }
42
GetForegroundTaskRunner(v8::Isolate * isolate)43 std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
44 v8::Isolate* isolate) override {
45 return platform_->GetForegroundTaskRunner(isolate);
46 }
47
NumberOfWorkerThreads()48 int NumberOfWorkerThreads() override {
49 // The predictable platform executes everything on the main thread, but we
50 // still pretend to have the default number of worker threads to not
51 // unnecessarily change behaviour of the platform.
52 return platform_->NumberOfWorkerThreads();
53 }
54
CallOnWorkerThread(std::unique_ptr<Task> task)55 void CallOnWorkerThread(std::unique_ptr<Task> task) override {
56 // We post worker tasks on the foreground task runner of the
57 // {kProcessGlobalPredictablePlatformWorkerTaskQueue} isolate. The task
58 // queue of the {kProcessGlobalPredictablePlatformWorkerTaskQueue} isolate
59 // is then executed on the main thread to achieve predictable behavior.
60 //
61 // In this context here it is okay to call {GetForegroundTaskRunner} from a
62 // background thread. The reason is that code is executed sequentially with
63 // the PredictablePlatform, and that the {DefaultPlatform} does not access
64 // the isolate but only uses it as the key in a HashMap.
65 GetForegroundTaskRunner(kProcessGlobalPredictablePlatformWorkerTaskQueue)
66 ->PostTask(std::move(task));
67 }
68
CallDelayedOnWorkerThread(std::unique_ptr<Task> task,double delay_in_seconds)69 void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
70 double delay_in_seconds) override {
71 // Never run delayed tasks.
72 }
73
IdleTasksEnabled(Isolate * isolate)74 bool IdleTasksEnabled(Isolate* isolate) override { return false; }
75
PostJob(TaskPriority priority,std::unique_ptr<JobTask> job_task)76 std::unique_ptr<JobHandle> PostJob(
77 TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
78 // Do not call {platform_->PostJob} here, as this would create a job that
79 // posts tasks directly to the underlying default platform.
80 return platform::NewDefaultJobHandle(this, priority, std::move(job_task),
81 NumberOfWorkerThreads());
82 }
83
MonotonicallyIncreasingTime()84 double MonotonicallyIncreasingTime() override {
85 // In predictable mode, there should be no (observable) concurrency, but we
86 // still run some tests that explicitly specify '--predictable' in the
87 // '--isolates' variant, where several threads run the same test in
88 // different isolates. To avoid TSan issues in that scenario we use atomic
89 // increments here.
90 uint64_t synthetic_time =
91 synthetic_time_.fetch_add(1, std::memory_order_relaxed);
92 return 1e-5 * synthetic_time;
93 }
94
CurrentClockTimeMillis()95 double CurrentClockTimeMillis() override {
96 return MonotonicallyIncreasingTime() * base::Time::kMillisecondsPerSecond;
97 }
98
GetTracingController()99 v8::TracingController* GetTracingController() override {
100 return platform_->GetTracingController();
101 }
102
platform() const103 Platform* platform() const { return platform_.get(); }
104
105 private:
106 std::atomic<uint64_t> synthetic_time_{0};
107 std::unique_ptr<Platform> platform_;
108 };
109
MakePredictablePlatform(std::unique_ptr<Platform> platform)110 std::unique_ptr<Platform> MakePredictablePlatform(
111 std::unique_ptr<Platform> platform) {
112 return std::make_unique<PredictablePlatform>(std::move(platform));
113 }
114
115 class DelayedTasksPlatform final : public Platform {
116 public:
DelayedTasksPlatform(std::unique_ptr<Platform> platform)117 explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform)
118 : platform_(std::move(platform)) {
119 DCHECK_NOT_NULL(platform_);
120 }
121
DelayedTasksPlatform(std::unique_ptr<Platform> platform,int64_t random_seed)122 explicit DelayedTasksPlatform(std::unique_ptr<Platform> platform,
123 int64_t random_seed)
124 : platform_(std::move(platform)), rng_(random_seed) {
125 DCHECK_NOT_NULL(platform_);
126 }
127
128 DelayedTasksPlatform(const DelayedTasksPlatform&) = delete;
129 DelayedTasksPlatform& operator=(const DelayedTasksPlatform&) = delete;
130
~DelayedTasksPlatform()131 ~DelayedTasksPlatform() override {
132 // When the platform shuts down, all task runners must be freed.
133 DCHECK_EQ(0, delayed_task_runners_.size());
134 }
135
GetPageAllocator()136 PageAllocator* GetPageAllocator() override {
137 return platform_->GetPageAllocator();
138 }
139
OnCriticalMemoryPressure()140 void OnCriticalMemoryPressure() override {
141 platform_->OnCriticalMemoryPressure();
142 }
143
OnCriticalMemoryPressure(size_t length)144 bool OnCriticalMemoryPressure(size_t length) override {
145 return platform_->OnCriticalMemoryPressure(length);
146 }
147
GetForegroundTaskRunner(v8::Isolate * isolate)148 std::shared_ptr<TaskRunner> GetForegroundTaskRunner(
149 v8::Isolate* isolate) override {
150 std::shared_ptr<TaskRunner> runner =
151 platform_->GetForegroundTaskRunner(isolate);
152
153 base::MutexGuard lock_guard(&mutex_);
154 // Check if we can re-materialize the weak ptr in our map.
155 std::weak_ptr<DelayedTaskRunner>& weak_delayed_runner =
156 delayed_task_runners_[runner.get()];
157 std::shared_ptr<DelayedTaskRunner> delayed_runner =
158 weak_delayed_runner.lock();
159
160 if (!delayed_runner) {
161 // Create a new {DelayedTaskRunner} and keep a weak reference in our map.
162 delayed_runner.reset(new DelayedTaskRunner(runner, this),
163 DelayedTaskRunnerDeleter{});
164 weak_delayed_runner = delayed_runner;
165 }
166
167 return std::move(delayed_runner);
168 }
169
NumberOfWorkerThreads()170 int NumberOfWorkerThreads() override {
171 return platform_->NumberOfWorkerThreads();
172 }
173
CallOnWorkerThread(std::unique_ptr<Task> task)174 void CallOnWorkerThread(std::unique_ptr<Task> task) override {
175 platform_->CallOnWorkerThread(MakeDelayedTask(std::move(task)));
176 }
177
CallDelayedOnWorkerThread(std::unique_ptr<Task> task,double delay_in_seconds)178 void CallDelayedOnWorkerThread(std::unique_ptr<Task> task,
179 double delay_in_seconds) override {
180 platform_->CallDelayedOnWorkerThread(MakeDelayedTask(std::move(task)),
181 delay_in_seconds);
182 }
183
IdleTasksEnabled(Isolate * isolate)184 bool IdleTasksEnabled(Isolate* isolate) override {
185 return platform_->IdleTasksEnabled(isolate);
186 }
187
PostJob(TaskPriority priority,std::unique_ptr<JobTask> job_task)188 std::unique_ptr<JobHandle> PostJob(
189 TaskPriority priority, std::unique_ptr<JobTask> job_task) override {
190 return platform_->PostJob(priority, MakeDelayedJob(std::move(job_task)));
191 }
192
MonotonicallyIncreasingTime()193 double MonotonicallyIncreasingTime() override {
194 return platform_->MonotonicallyIncreasingTime();
195 }
196
CurrentClockTimeMillis()197 double CurrentClockTimeMillis() override {
198 return platform_->CurrentClockTimeMillis();
199 }
200
GetTracingController()201 v8::TracingController* GetTracingController() override {
202 return platform_->GetTracingController();
203 }
204
205 private:
206 class DelayedTaskRunnerDeleter;
207 class DelayedTaskRunner final : public TaskRunner {
208 public:
DelayedTaskRunner(std::shared_ptr<TaskRunner> task_runner,DelayedTasksPlatform * platform)209 DelayedTaskRunner(std::shared_ptr<TaskRunner> task_runner,
210 DelayedTasksPlatform* platform)
211 : task_runner_(task_runner), platform_(platform) {}
212
PostTask(std::unique_ptr<Task> task)213 void PostTask(std::unique_ptr<Task> task) final {
214 task_runner_->PostTask(platform_->MakeDelayedTask(std::move(task)));
215 }
216
PostNonNestableTask(std::unique_ptr<Task> task)217 void PostNonNestableTask(std::unique_ptr<Task> task) final {
218 task_runner_->PostNonNestableTask(
219 platform_->MakeDelayedTask(std::move(task)));
220 }
221
PostDelayedTask(std::unique_ptr<Task> task,double delay_in_seconds)222 void PostDelayedTask(std::unique_ptr<Task> task,
223 double delay_in_seconds) final {
224 task_runner_->PostDelayedTask(platform_->MakeDelayedTask(std::move(task)),
225 delay_in_seconds);
226 }
227
PostIdleTask(std::unique_ptr<IdleTask> task)228 void PostIdleTask(std::unique_ptr<IdleTask> task) final {
229 task_runner_->PostIdleTask(
230 platform_->MakeDelayedIdleTask(std::move(task)));
231 }
232
IdleTasksEnabled()233 bool IdleTasksEnabled() final { return task_runner_->IdleTasksEnabled(); }
234
NonNestableTasksEnabled() const235 bool NonNestableTasksEnabled() const final {
236 return task_runner_->NonNestableTasksEnabled();
237 }
238
239 private:
240 friend class DelayedTaskRunnerDeleter;
241 std::shared_ptr<TaskRunner> task_runner_;
242 DelayedTasksPlatform* platform_;
243 };
244
245 class DelayedTaskRunnerDeleter {
246 public:
operator ()(DelayedTaskRunner * runner) const247 void operator()(DelayedTaskRunner* runner) const {
248 TaskRunner* original_runner = runner->task_runner_.get();
249 base::MutexGuard lock_guard(&runner->platform_->mutex_);
250 auto& delayed_task_runners = runner->platform_->delayed_task_runners_;
251 DCHECK_EQ(1, delayed_task_runners.count(original_runner));
252 delayed_task_runners.erase(original_runner);
253 }
254 };
255
256 class DelayedTask final : public Task {
257 public:
DelayedTask(std::unique_ptr<Task> task,int32_t delay_ms)258 DelayedTask(std::unique_ptr<Task> task, int32_t delay_ms)
259 : task_(std::move(task)), delay_ms_(delay_ms) {}
260
Run()261 void Run() override {
262 base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
263 task_->Run();
264 }
265
266 private:
267 std::unique_ptr<Task> task_;
268 int32_t delay_ms_;
269 };
270
271 class DelayedIdleTask final : public IdleTask {
272 public:
DelayedIdleTask(std::unique_ptr<IdleTask> task,int32_t delay_ms)273 DelayedIdleTask(std::unique_ptr<IdleTask> task, int32_t delay_ms)
274 : task_(std::move(task)), delay_ms_(delay_ms) {}
275
Run(double deadline_in_seconds)276 void Run(double deadline_in_seconds) override {
277 base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
278 task_->Run(deadline_in_seconds);
279 }
280
281 private:
282 std::unique_ptr<IdleTask> task_;
283 int32_t delay_ms_;
284 };
285
286 class DelayedJob final : public JobTask {
287 public:
DelayedJob(std::unique_ptr<JobTask> job_task,int32_t delay_ms)288 DelayedJob(std::unique_ptr<JobTask> job_task, int32_t delay_ms)
289 : job_task_(std::move(job_task)), delay_ms_(delay_ms) {}
290
Run(JobDelegate * delegate)291 void Run(JobDelegate* delegate) override {
292 // If this job is being executed via worker tasks (as e.g. the
293 // {DefaultJobHandle} implementation does it), the worker task would
294 // already include a delay. In order to not depend on that, we add our own
295 // delay here anyway.
296 base::OS::Sleep(base::TimeDelta::FromMicroseconds(delay_ms_));
297 job_task_->Run(delegate);
298 }
299
GetMaxConcurrency(size_t worker_count) const300 size_t GetMaxConcurrency(size_t worker_count) const override {
301 return job_task_->GetMaxConcurrency(worker_count);
302 }
303
304 private:
305 std::unique_ptr<JobTask> job_task_;
306 int32_t delay_ms_;
307 };
308
309 std::unique_ptr<Platform> platform_;
310
311 // The Mutex protects the RNG, which is used by foreground and background
312 // threads, and the {delayed_task_runners_} map might be accessed concurrently
313 // by the shared_ptr destructor.
314 base::Mutex mutex_;
315 base::RandomNumberGenerator rng_;
316 std::unordered_map<TaskRunner*, std::weak_ptr<DelayedTaskRunner>>
317 delayed_task_runners_;
318
GetRandomDelayInMilliseconds()319 int32_t GetRandomDelayInMilliseconds() {
320 base::MutexGuard lock_guard(&mutex_);
321 double delay_fraction = rng_.NextDouble();
322 // Sleep up to 100ms (100000us). Square {delay_fraction} to shift
323 // distribution towards shorter sleeps.
324 return 1e5 * (delay_fraction * delay_fraction);
325 }
326
MakeDelayedTask(std::unique_ptr<Task> task)327 std::unique_ptr<Task> MakeDelayedTask(std::unique_ptr<Task> task) {
328 return std::make_unique<DelayedTask>(std::move(task),
329 GetRandomDelayInMilliseconds());
330 }
331
MakeDelayedIdleTask(std::unique_ptr<IdleTask> task)332 std::unique_ptr<IdleTask> MakeDelayedIdleTask(
333 std::unique_ptr<IdleTask> task) {
334 return std::make_unique<DelayedIdleTask>(std::move(task),
335 GetRandomDelayInMilliseconds());
336 }
337
MakeDelayedJob(std::unique_ptr<JobTask> task)338 std::unique_ptr<JobTask> MakeDelayedJob(std::unique_ptr<JobTask> task) {
339 return std::make_unique<DelayedJob>(std::move(task),
340 GetRandomDelayInMilliseconds());
341 }
342 };
343
MakeDelayedTasksPlatform(std::unique_ptr<Platform> platform,int64_t random_seed)344 std::unique_ptr<Platform> MakeDelayedTasksPlatform(
345 std::unique_ptr<Platform> platform, int64_t random_seed) {
346 if (random_seed) {
347 return std::make_unique<DelayedTasksPlatform>(std::move(platform),
348 random_seed);
349 }
350 return std::make_unique<DelayedTasksPlatform>(std::move(platform));
351 }
352
353 } // namespace v8
354