OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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 "base/task_scheduler/thread_pool.h" | |
6 | |
7 #include <stddef.h> | |
8 | |
9 #include <unordered_set> | |
10 #include <vector> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/bind_helpers.h" | |
14 #include "base/macros.h" | |
15 #include "base/memory/scoped_ptr.h" | |
16 #include "base/synchronization/condition_variable.h" | |
17 #include "base/synchronization/waitable_event.h" | |
18 #include "base/task_runner.h" | |
19 #include "base/task_scheduler/scheduler_lock.h" | |
20 #include "base/task_scheduler/task_tracker.h" | |
21 #include "base/threading/platform_thread.h" | |
22 #include "base/threading/simple_thread.h" | |
23 #include "testing/gtest/include/gtest/gtest.h" | |
24 | |
25 namespace base { | |
26 namespace internal { | |
27 namespace { | |
28 | |
29 const size_t kNumRepetitions = 4; | |
30 const size_t kNumThreadsInThreadPool = 4; | |
31 const size_t kNumTaskRunners = 4; | |
32 const size_t kNumThreadsPostingTasksPerTaskRunner = 2; | |
33 const size_t kNumTasksPostedPerThread = 150; | |
34 | |
35 class TaskSchedulerThreadPoolTest : public testing::Test { | |
36 protected: | |
37 TaskSchedulerThreadPoolTest() : cv_(lock_.CreateConditionVariable()) {} | |
38 | |
39 void SetUp() override { | |
40 thread_pool_ = ThreadPool::CreateThreadPool( | |
41 ThreadPriority::NORMAL, kNumThreadsInThreadPool, | |
42 Bind(&TaskSchedulerThreadPoolTest::RanTaskFromSequenceCallback, | |
43 Unretained(this)), | |
44 &task_tracker_); | |
45 ASSERT_TRUE(thread_pool_); | |
46 } | |
47 | |
48 void TearDown() override { thread_pool_->JoinForTesting(); } | |
49 | |
50 // Waits for all tasks returned by CreateTask() to run. | |
51 void WaitForAllTasksToRun() { | |
52 AutoSchedulerLock auto_lock(lock_); | |
53 while (run_tasks_.size() < num_created_tasks_) | |
54 cv_->Wait(); | |
55 } | |
56 | |
57 size_t NumRunTasks() const { | |
58 AutoSchedulerLock auto_lock(lock_); | |
59 return run_tasks_.size(); | |
60 } | |
61 | |
62 scoped_ptr<ThreadPool> thread_pool_; | |
63 | |
64 public: | |
65 // |task_runner| is the TaskRunner through which the task will be posted. If | |
66 // |post_nested_task| is true, the created task will post a new task through | |
67 // |task_runner| when it runs. | |
68 Closure CreateTask(scoped_refptr<TaskRunner> task_runner, | |
69 bool post_nested_task) { | |
70 AutoSchedulerLock auto_lock(lock_); | |
71 return Bind(&TaskSchedulerThreadPoolTest::RunTaskCallback, Unretained(this), | |
72 num_created_tasks_++, task_runner, post_nested_task); | |
73 } | |
74 | |
75 private: | |
76 void RanTaskFromSequenceCallback(const SchedulerWorkerThread* worker_thread, | |
77 scoped_refptr<Sequence> sequence) { | |
78 if (!sequence->PopTask()) { | |
79 const SequenceSortKey sort_key(sequence->GetSortKey()); | |
80 thread_pool_->ReinsertSequence(worker_thread, std::move(sequence), | |
81 sort_key); | |
82 } | |
83 } | |
84 | |
85 // |task_index| is a unique index for this task. |task_runner| is the | |
86 // TaskRunner through which the task was posted. If |post_nested_task| is | |
87 // true, the callback will post a new task through |task_runner|. | |
88 void RunTaskCallback(size_t task_index, | |
89 scoped_refptr<TaskRunner> task_runner, | |
90 bool post_nested_task) { | |
91 if (post_nested_task) | |
92 task_runner->PostTask(FROM_HERE, CreateTask(task_runner, false)); | |
93 | |
94 EXPECT_TRUE(task_runner->RunsTasksOnCurrentThread()); | |
95 | |
96 AutoSchedulerLock auto_lock(lock_); | |
97 | |
98 if (run_tasks_.find(task_index) != run_tasks_.end()) | |
99 ADD_FAILURE() << "A task ran more than once."; | |
100 run_tasks_.insert(task_index); | |
101 | |
102 cv_->Signal(); | |
103 } | |
104 | |
105 TaskTracker task_tracker_; | |
106 | |
107 // Synchronizes access to all members below. | |
108 mutable SchedulerLock lock_; | |
109 | |
110 // Condition variable signaled when a task completes its execution. | |
111 scoped_ptr<ConditionVariable> cv_; | |
112 | |
113 // Number of tasks returned by CreateTask(). | |
114 size_t num_created_tasks_ = 0; | |
115 | |
116 // Indexes of tasks that ran. | |
117 std::unordered_set<size_t> run_tasks_; | |
118 | |
119 DISALLOW_COPY_AND_ASSIGN(TaskSchedulerThreadPoolTest); | |
120 }; | |
121 | |
122 class ThreadPostingTasks : public SimpleThread { | |
123 public: | |
124 // If |post_nested_task| is true, each task posted by this thread will post | |
125 // another task when it runs. | |
126 ThreadPostingTasks(scoped_refptr<TaskRunner> task_runner, | |
127 bool post_nested_task, | |
128 TaskSchedulerThreadPoolTest* test) | |
129 : SimpleThread("ThreadPostingTasks"), | |
130 task_runner_(std::move(task_runner)), | |
131 post_nested_task_(post_nested_task), | |
132 test_(test) {} | |
133 | |
134 private: | |
135 void Run() override { | |
136 EXPECT_FALSE(task_runner_->RunsTasksOnCurrentThread()); | |
137 | |
138 for (size_t i = 0; i < kNumTasksPostedPerThread; ++i) { | |
139 task_runner_->PostTask( | |
140 FROM_HERE, test_->CreateTask(task_runner_, post_nested_task_)); | |
141 } | |
142 } | |
143 | |
144 scoped_refptr<TaskRunner> task_runner_; | |
145 const bool post_nested_task_; | |
146 TaskSchedulerThreadPoolTest* const test_; | |
147 | |
148 DISALLOW_COPY_AND_ASSIGN(ThreadPostingTasks); | |
149 }; | |
150 | |
151 TEST_F(TaskSchedulerThreadPoolTest, PostParallelTasks) { | |
152 // Repeat the test |kNumRepetitions| times, waiting for all | |
153 // SchedulerWorkerThreads to become idle between each repetition. This ensures | |
154 // that TaskRunners can wake up SchedulerWorkerThreads that have added | |
155 // themselves to the stack of idle SchedulerWorkerThreads of the ThreadPool. | |
156 for (size_t i = 0; i < kNumRepetitions; ++i) { | |
157 // Create |kNumTaskRunners| * |kNumThreadsPostingTasksPerTaskRunner| threads | |
158 // that will post tasks to |kNumTaskRunners| PARALLEL TaskRunners. | |
159 std::vector<scoped_ptr<ThreadPostingTasks>> threads_posting_tasks; | |
160 for (size_t j = 0; j < kNumTaskRunners; ++j) { | |
161 scoped_refptr<TaskRunner> task_runner = | |
162 thread_pool_->CreateTaskRunnerWithTraits(TaskTraits(), | |
163 ExecutionMode::PARALLEL); | |
164 for (size_t k = 0; k < kNumThreadsPostingTasksPerTaskRunner; ++k) { | |
165 threads_posting_tasks.push_back( | |
166 make_scoped_ptr(new ThreadPostingTasks(task_runner, false, this))); | |
167 threads_posting_tasks.back()->Start(); | |
168 } | |
169 } | |
170 | |
171 for (const auto& thread_posting_tasks : threads_posting_tasks) | |
172 thread_posting_tasks->Join(); | |
173 | |
174 WaitForAllTasksToRun(); | |
175 EXPECT_EQ((i + 1) * kNumTaskRunners * kNumThreadsPostingTasksPerTaskRunner * | |
176 kNumTasksPostedPerThread, | |
177 NumRunTasks()); | |
178 thread_pool_->WaitForAllWorkerThreadsIdleForTesting(); | |
179 } | |
180 } | |
181 | |
182 TEST_F(TaskSchedulerThreadPoolTest, PostParallelTasksWithNestedPostTasks) { | |
183 // Repeat the test |kNumRepetitions| times, waiting for all | |
184 // SchedulerWorkerThreads to become idle between each repetition. This ensures | |
185 // that TaskRunners can wake up SchedulerWorkerThreads that have added | |
186 // themselves to the stack of idle SchedulerWorkerThreads of the ThreadPool. | |
187 for (size_t i = 0; i < kNumRepetitions; ++i) { | |
188 // Create |kNumTaskRunners| * |kNumThreadsPostingTasksPerTaskRunner| threads | |
189 // that will post tasks to |kNumTaskRunners| PARALLEL TaskRunners. Each task | |
190 // posted by these threads will post another task when it runs. | |
191 std::vector<scoped_ptr<ThreadPostingTasks>> threads_posting_tasks; | |
192 for (size_t j = 0; j < kNumTaskRunners; ++j) { | |
193 auto task_runner = thread_pool_->CreateTaskRunnerWithTraits( | |
194 TaskTraits(), ExecutionMode::PARALLEL); | |
195 for (size_t k = 0; k < kNumThreadsPostingTasksPerTaskRunner; ++k) { | |
196 threads_posting_tasks.push_back( | |
197 make_scoped_ptr(new ThreadPostingTasks(task_runner, true, this))); | |
198 threads_posting_tasks.back()->Start(); | |
199 } | |
200 } | |
201 | |
202 for (const auto& thread_posting_tasks : threads_posting_tasks) | |
203 thread_posting_tasks->Join(); | |
204 | |
205 WaitForAllTasksToRun(); | |
206 EXPECT_EQ(2 * (i + 1) * kNumTaskRunners * | |
207 kNumThreadsPostingTasksPerTaskRunner * | |
208 kNumTasksPostedPerThread, | |
209 NumRunTasks()); | |
210 thread_pool_->WaitForAllWorkerThreadsIdleForTesting(); | |
211 } | |
212 } | |
213 | |
214 TEST_F(TaskSchedulerThreadPoolTest, PostParallelTasksWithBlockedTasks) { | |
215 // Post |kNumThreadsInThreadPool| - 1 tasks that block until |event| is | |
216 // signaled. | |
217 WaitableEvent event( | |
218 true, // Manual reset, to be able to wake up multiple waiters at once. | |
219 false); // Not signaled initially. | |
220 auto task_runner = thread_pool_->CreateTaskRunnerWithTraits( | |
221 TaskTraits(), ExecutionMode::PARALLEL); | |
222 for (size_t i = 0; i < (kNumThreadsInThreadPool - 1); ++i) { | |
223 task_runner->PostTask(FROM_HERE, | |
224 Bind(&WaitableEvent::Wait, Unretained(&event))); | |
225 } | |
226 | |
227 // Post |kNumTasksPostedPerThread| tasks that should run despite the fact | |
228 // that all threads in |thread_pool_| are busy except one. | |
229 for (size_t i = 0; i < kNumTasksPostedPerThread; ++i) | |
230 task_runner->PostTask(FROM_HERE, CreateTask(task_runner, false)); | |
231 | |
232 WaitForAllTasksToRun(); | |
233 event.Signal(); | |
234 thread_pool_->WaitForAllWorkerThreadsIdleForTesting(); | |
235 } | |
robliao
2016/03/31 22:48:56
Would it be useful to have a test that saturates t
fdoray
2016/04/01 16:02:52
Done. TaskSchedulerThreadPoolTest.Saturate
| |
236 | |
237 } // namespace | |
238 } // namespace internal | |
239 } // namespace base | |
OLD | NEW |