1 // Copyright 2017 The Chromium 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 "third_party/blink/renderer/platform/scheduler/public/worker_scheduler.h"
6
7 #include <memory>
8 #include "base/bind.h"
9 #include "base/macros.h"
10 #include "base/task/sequence_manager/test/sequence_manager_for_test.h"
11 #include "base/test/simple_test_tick_clock.h"
12 #include "base/test/test_mock_time_task_runner.h"
13 #include "testing/gmock/include/gmock/gmock.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "third_party/blink/renderer/platform/scheduler/common/throttling/task_queue_throttler.h"
16 #include "third_party/blink/renderer/platform/scheduler/worker/worker_thread_scheduler.h"
17 #include "third_party/blink/renderer/platform/wtf/functional.h"
18
19 // TODO(crbug.com/960984): Fix memory leaks in tests and re-enable on LSAN.
20 #ifdef LEAK_SANITIZER
21 #define MAYBE_PausableTasks DISABLED_PausableTasks
22 #define MAYBE_NestedPauseHandlesTasks DISABLED_NestedPauseHandlesTasks
23 #else
24 #define MAYBE_PausableTasks PausableTasks
25 #define MAYBE_NestedPauseHandlesTasks NestedPauseHandlesTasks
26 #endif
27
28 using testing::ElementsAre;
29 using testing::ElementsAreArray;
30
31 namespace blink {
32 namespace scheduler {
33 // To avoid symbol collisions in jumbo builds.
34 namespace worker_scheduler_unittest {
35
AppendToVectorTestTask(Vector<String> * vector,String value)36 void AppendToVectorTestTask(Vector<String>* vector, String value) {
37 vector->push_back(value);
38 }
39
RunChainedTask(scoped_refptr<base::sequence_manager::TaskQueue> task_queue,int count,base::TimeDelta duration,scoped_refptr<base::TestMockTimeTaskRunner> environment,Vector<base::TimeTicks> * tasks)40 void RunChainedTask(scoped_refptr<base::sequence_manager::TaskQueue> task_queue,
41 int count,
42 base::TimeDelta duration,
43 scoped_refptr<base::TestMockTimeTaskRunner> environment,
44 Vector<base::TimeTicks>* tasks) {
45 tasks->push_back(environment->GetMockTickClock()->NowTicks());
46
47 environment->AdvanceMockTickClock(duration);
48
49 if (count == 1)
50 return;
51
52 // Add a delay of 50ms to ensure that wake-up based throttling does not affect
53 // us.
54 task_queue->task_runner()->PostDelayedTask(
55 FROM_HERE,
56 base::BindOnce(&RunChainedTask, task_queue, count - 1, duration,
57 environment, base::Unretained(tasks)),
58 base::TimeDelta::FromMilliseconds(50));
59 }
60
61 class WorkerThreadSchedulerForTest : public WorkerThreadScheduler {
62 public:
63 // |manager| and |proxy| must remain valid for the entire lifetime of this
64 // object.
WorkerThreadSchedulerForTest(ThreadType thread_type,base::sequence_manager::SequenceManager * manager,WorkerSchedulerProxy * proxy)65 WorkerThreadSchedulerForTest(ThreadType thread_type,
66 base::sequence_manager::SequenceManager* manager,
67 WorkerSchedulerProxy* proxy)
68 : WorkerThreadScheduler(thread_type, manager, proxy) {}
69
worker_schedulers()70 const HashSet<WorkerScheduler*>& worker_schedulers() {
71 return GetWorkerSchedulersForTesting();
72 }
73
74 using WorkerThreadScheduler::CreateTaskQueueThrottler;
75 using WorkerThreadScheduler::SetCPUTimeBudgetPoolForTesting;
76 };
77
78 class WorkerSchedulerForTest : public WorkerScheduler {
79 public:
WorkerSchedulerForTest(WorkerThreadSchedulerForTest * thread_scheduler)80 explicit WorkerSchedulerForTest(
81 WorkerThreadSchedulerForTest* thread_scheduler)
82 : WorkerScheduler(thread_scheduler, nullptr) {}
83
84 using WorkerScheduler::ThrottleableTaskQueue;
85 using WorkerScheduler::UnpausableTaskQueue;
86 };
87
88 class WorkerSchedulerTest : public testing::Test {
89 public:
WorkerSchedulerTest()90 WorkerSchedulerTest()
91 : mock_task_runner_(new base::TestMockTimeTaskRunner()),
92 sequence_manager_(
93 base::sequence_manager::SequenceManagerForTest::Create(
94 nullptr,
95 mock_task_runner_,
96 mock_task_runner_->GetMockTickClock())),
97 scheduler_(new WorkerThreadSchedulerForTest(ThreadType::kTestThread,
98 sequence_manager_.get(),
99 nullptr /* proxy */)) {
100 mock_task_runner_->AdvanceMockTickClock(
101 base::TimeDelta::FromMicroseconds(5000));
102 }
103
104 ~WorkerSchedulerTest() override = default;
105
SetUp()106 void SetUp() override {
107 scheduler_->Init();
108 worker_scheduler_ =
109 std::make_unique<WorkerSchedulerForTest>(scheduler_.get());
110 }
111
TearDown()112 void TearDown() override {
113 if (worker_scheduler_) {
114 worker_scheduler_->Dispose();
115 worker_scheduler_.reset();
116 }
117 }
118
GetClock()119 const base::TickClock* GetClock() {
120 return mock_task_runner_->GetMockTickClock();
121 }
122
RunUntilIdle()123 void RunUntilIdle() { mock_task_runner_->FastForwardUntilNoTasksRemain(); }
124
125 // Helper for posting a task.
PostTestTask(Vector<String> * run_order,const String & task_descriptor,TaskType task_type)126 void PostTestTask(Vector<String>* run_order,
127 const String& task_descriptor,
128 TaskType task_type) {
129 worker_scheduler_->GetTaskRunner(task_type)->PostTask(
130 FROM_HERE, WTF::Bind(&AppendToVectorTestTask,
131 WTF::Unretained(run_order), task_descriptor));
132 }
133
134 protected:
135 scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
136 std::unique_ptr<base::sequence_manager::SequenceManagerForTest>
137 sequence_manager_;
138 std::unique_ptr<WorkerThreadSchedulerForTest> scheduler_;
139 std::unique_ptr<WorkerSchedulerForTest> worker_scheduler_;
140
141 DISALLOW_COPY_AND_ASSIGN(WorkerSchedulerTest);
142 };
143
TEST_F(WorkerSchedulerTest,TestPostTasks)144 TEST_F(WorkerSchedulerTest, TestPostTasks) {
145 Vector<String> run_order;
146 PostTestTask(&run_order, "T1", TaskType::kInternalTest);
147 PostTestTask(&run_order, "T2", TaskType::kInternalTest);
148 RunUntilIdle();
149 PostTestTask(&run_order, "T3", TaskType::kInternalTest);
150 RunUntilIdle();
151 EXPECT_THAT(run_order, testing::ElementsAre("T1", "T2", "T3"));
152
153 // Tasks should not run after the scheduler is disposed of.
154 worker_scheduler_->Dispose();
155 run_order.clear();
156 PostTestTask(&run_order, "T4", TaskType::kInternalTest);
157 PostTestTask(&run_order, "T5", TaskType::kInternalTest);
158 RunUntilIdle();
159 EXPECT_TRUE(run_order.IsEmpty());
160
161 worker_scheduler_.reset();
162 }
163
TEST_F(WorkerSchedulerTest,RegisterWorkerSchedulers)164 TEST_F(WorkerSchedulerTest, RegisterWorkerSchedulers) {
165 EXPECT_THAT(scheduler_->worker_schedulers(),
166 testing::ElementsAre(worker_scheduler_.get()));
167
168 std::unique_ptr<WorkerSchedulerForTest> worker_scheduler2 =
169 std::make_unique<WorkerSchedulerForTest>(scheduler_.get());
170
171 EXPECT_THAT(scheduler_->worker_schedulers(),
172 testing::UnorderedElementsAre(worker_scheduler_.get(),
173 worker_scheduler2.get()));
174
175 worker_scheduler_->Dispose();
176 worker_scheduler_.reset();
177
178 EXPECT_THAT(scheduler_->worker_schedulers(),
179 testing::ElementsAre(worker_scheduler2.get()));
180
181 worker_scheduler2->Dispose();
182
183 EXPECT_THAT(scheduler_->worker_schedulers(), testing::ElementsAre());
184 }
185
TEST_F(WorkerSchedulerTest,ThrottleWorkerScheduler)186 TEST_F(WorkerSchedulerTest, ThrottleWorkerScheduler) {
187 scheduler_->CreateTaskQueueThrottler();
188
189 EXPECT_FALSE(scheduler_->task_queue_throttler()->IsThrottled(
190 worker_scheduler_->ThrottleableTaskQueue().get()));
191
192 scheduler_->OnLifecycleStateChanged(SchedulingLifecycleState::kThrottled);
193 EXPECT_TRUE(scheduler_->task_queue_throttler()->IsThrottled(
194 worker_scheduler_->ThrottleableTaskQueue().get()));
195
196 scheduler_->OnLifecycleStateChanged(SchedulingLifecycleState::kThrottled);
197 EXPECT_TRUE(scheduler_->task_queue_throttler()->IsThrottled(
198 worker_scheduler_->ThrottleableTaskQueue().get()));
199
200 // Ensure that two calls with kThrottled do not mess with throttling
201 // refcount.
202 scheduler_->OnLifecycleStateChanged(SchedulingLifecycleState::kNotThrottled);
203 EXPECT_FALSE(scheduler_->task_queue_throttler()->IsThrottled(
204 worker_scheduler_->ThrottleableTaskQueue().get()));
205 }
206
TEST_F(WorkerSchedulerTest,ThrottleWorkerScheduler_CreateThrottled)207 TEST_F(WorkerSchedulerTest, ThrottleWorkerScheduler_CreateThrottled) {
208 scheduler_->CreateTaskQueueThrottler();
209
210 scheduler_->OnLifecycleStateChanged(SchedulingLifecycleState::kThrottled);
211
212 std::unique_ptr<WorkerSchedulerForTest> worker_scheduler2 =
213 std::make_unique<WorkerSchedulerForTest>(scheduler_.get());
214
215 // Ensure that newly created scheduler is throttled.
216 EXPECT_TRUE(scheduler_->task_queue_throttler()->IsThrottled(
217 worker_scheduler2->ThrottleableTaskQueue().get()));
218
219 worker_scheduler2->Dispose();
220 }
221
TEST_F(WorkerSchedulerTest,ThrottleWorkerScheduler_RunThrottledTasks)222 TEST_F(WorkerSchedulerTest, ThrottleWorkerScheduler_RunThrottledTasks) {
223 scheduler_->CreateTaskQueueThrottler();
224 scheduler_->SetCPUTimeBudgetPoolForTesting(nullptr);
225
226 // Create a new |worker_scheduler| to ensure that it's properly initialised.
227 worker_scheduler_->Dispose();
228 worker_scheduler_ =
229 std::make_unique<WorkerSchedulerForTest>(scheduler_.get());
230
231 scheduler_->OnLifecycleStateChanged(SchedulingLifecycleState::kThrottled);
232
233 Vector<base::TimeTicks> tasks;
234
235 worker_scheduler_->ThrottleableTaskQueue()->task_runner()->PostTask(
236 FROM_HERE, base::BindOnce(&RunChainedTask,
237 worker_scheduler_->ThrottleableTaskQueue(), 5,
238 base::TimeDelta(), mock_task_runner_,
239 base::Unretained(&tasks)));
240
241 RunUntilIdle();
242
243 EXPECT_THAT(tasks,
244 ElementsAre(base::TimeTicks() + base::TimeDelta::FromSeconds(1),
245 base::TimeTicks() + base::TimeDelta::FromSeconds(2),
246 base::TimeTicks() + base::TimeDelta::FromSeconds(3),
247 base::TimeTicks() + base::TimeDelta::FromSeconds(4),
248 base::TimeTicks() + base::TimeDelta::FromSeconds(5)));
249 }
250
TEST_F(WorkerSchedulerTest,ThrottleWorkerScheduler_RunThrottledTasks_CPUBudget)251 TEST_F(WorkerSchedulerTest,
252 ThrottleWorkerScheduler_RunThrottledTasks_CPUBudget) {
253 scheduler_->CreateTaskQueueThrottler();
254
255 scheduler_->cpu_time_budget_pool()->SetTimeBudgetRecoveryRate(
256 GetClock()->NowTicks(), 0.01);
257
258 // Create a new |worker_scheduler| to ensure that it's properly initialised.
259 worker_scheduler_->Dispose();
260 worker_scheduler_ =
261 std::make_unique<WorkerSchedulerForTest>(scheduler_.get());
262
263 scheduler_->OnLifecycleStateChanged(SchedulingLifecycleState::kThrottled);
264
265 Vector<base::TimeTicks> tasks;
266
267 worker_scheduler_->ThrottleableTaskQueue()->task_runner()->PostTask(
268 FROM_HERE, base::BindOnce(&RunChainedTask,
269 worker_scheduler_->ThrottleableTaskQueue(), 5,
270 base::TimeDelta::FromMilliseconds(100),
271 mock_task_runner_, base::Unretained(&tasks)));
272
273 RunUntilIdle();
274
275 EXPECT_THAT(
276 tasks, ElementsAre(base::TimeTicks() + base::TimeDelta::FromSeconds(1),
277 base::TimeTicks() + base::TimeDelta::FromSeconds(11),
278 base::TimeTicks() + base::TimeDelta::FromSeconds(21),
279 base::TimeTicks() + base::TimeDelta::FromSeconds(31),
280 base::TimeTicks() + base::TimeDelta::FromSeconds(41)));
281 }
282
TEST_F(WorkerSchedulerTest,MAYBE_PausableTasks)283 TEST_F(WorkerSchedulerTest, MAYBE_PausableTasks) {
284 Vector<String> run_order;
285 auto pause_handle = worker_scheduler_->Pause();
286 // Tests interlacing pausable, throttable and unpausable tasks and
287 // ensures that the pausable & throttable tasks don't run when paused.
288 // Throttable
289 PostTestTask(&run_order, "T1", TaskType::kJavascriptTimerDelayedLowNesting);
290 // Pausable
291 PostTestTask(&run_order, "T2", TaskType::kNetworking);
292 // Unpausable
293 PostTestTask(&run_order, "T3", TaskType::kInternalTest);
294 RunUntilIdle();
295 EXPECT_THAT(run_order, testing::ElementsAre("T3"));
296 pause_handle.reset();
297 RunUntilIdle();
298
299 EXPECT_THAT(run_order, testing::ElementsAre("T3", "T1", "T2"));
300 }
301
TEST_F(WorkerSchedulerTest,MAYBE_NestedPauseHandlesTasks)302 TEST_F(WorkerSchedulerTest, MAYBE_NestedPauseHandlesTasks) {
303 Vector<String> run_order;
304 auto pause_handle = worker_scheduler_->Pause();
305 {
306 auto pause_handle2 = worker_scheduler_->Pause();
307 PostTestTask(&run_order, "T1", TaskType::kJavascriptTimerDelayedLowNesting);
308 PostTestTask(&run_order, "T2", TaskType::kNetworking);
309 }
310 RunUntilIdle();
311 EXPECT_EQ(0u, run_order.size());
312 pause_handle.reset();
313 RunUntilIdle();
314 EXPECT_THAT(run_order, testing::ElementsAre("T1", "T2"));
315 }
316
317 } // namespace worker_scheduler_unittest
318 } // namespace scheduler
319 } // namespace blink
320