Merge lp:~albaguirre/mir/recycle-compositor-threads-take2 into lp:mir
- recycle-compositor-threads-take2
- Merge into development-branch
Status: | Merged |
---|---|
Approved by: | Alberto Aguirre |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1897 |
Proposed branch: | lp:~albaguirre/mir/recycle-compositor-threads-take2 |
Merge into: | lp:mir |
Diff against target: |
661 lines (+538/-8) 9 files modified
include.private/server/mir/thread/basic_thread_pool.h (+63/-0) src/server/CMakeLists.txt (+2/-0) src/server/compositor/multi_threaded_compositor.cpp (+8/-5) src/server/compositor/multi_threaded_compositor.h (+5/-3) src/server/thread/CMakeLists.txt (+12/-0) src/server/thread/basic_thread_pool.cpp (+221/-0) tests/unit-tests/CMakeLists.txt (+1/-0) tests/unit-tests/thread/CMakeLists.txt (+5/-0) tests/unit-tests/thread/test_basic_thread_pool.cpp (+221/-0) |
To merge this branch: | bzr merge lp:~albaguirre/mir/recycle-compositor-threads-take2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Alexandros Frantzis (community) | Approve | ||
Review via email: mp+233609@code.launchpad.net |
Commit message
Recycle compositor threads by using a thread pool (LP: #1362841)
Description of the change
Recycle compositor threads by using a thread pool
Adds a basic thread pool placed under server instead of common as I wanted to use mir::terminate_
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1896
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alexandros Frantzis (afrantzis) wrote : | # |
Looks good.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Alberto Aguirre (albaguirre) wrote : | # |
^--DemoPrivateP
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === added directory 'include.private/server/mir/thread' | |||
2 | === added file 'include.private/server/mir/thread/basic_thread_pool.h' | |||
3 | --- include.private/server/mir/thread/basic_thread_pool.h 1970-01-01 00:00:00 +0000 | |||
4 | +++ include.private/server/mir/thread/basic_thread_pool.h 2014-09-06 14:29:21 +0000 | |||
5 | @@ -0,0 +1,63 @@ | |||
6 | 1 | /* | ||
7 | 2 | * Copyright © 2014 Canonical Ltd. | ||
8 | 3 | * | ||
9 | 4 | * This program is free software: you can redistribute it and/or modify | ||
10 | 5 | * it under the terms of the GNU Lesser General Public License version 3 as | ||
11 | 6 | * published by the Free Software Foundation. | ||
12 | 7 | * | ||
13 | 8 | * This program is distributed in the hope that it will be useful, | ||
14 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | 11 | * GNU Lesser General Public License for more details. | ||
17 | 12 | * | ||
18 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
19 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
20 | 15 | * | ||
21 | 16 | * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> | ||
22 | 17 | */ | ||
23 | 18 | |||
24 | 19 | #ifndef MIR_THREAD_BASIC_THREAD_POOL_H_ | ||
25 | 20 | #define MIR_THREAD_BASIC_THREAD_POOL_H_ | ||
26 | 21 | |||
27 | 22 | #include <functional> | ||
28 | 23 | #include <future> | ||
29 | 24 | #include <vector> | ||
30 | 25 | #include <memory> | ||
31 | 26 | #include <mutex> | ||
32 | 27 | |||
33 | 28 | namespace mir | ||
34 | 29 | { | ||
35 | 30 | namespace thread | ||
36 | 31 | { | ||
37 | 32 | |||
38 | 33 | class WorkerThread; | ||
39 | 34 | class BasicThreadPool | ||
40 | 35 | { | ||
41 | 36 | public: | ||
42 | 37 | BasicThreadPool(int min_threads); | ||
43 | 38 | ~BasicThreadPool(); | ||
44 | 39 | |||
45 | 40 | std::future<void> run(std::function<void()> const& task); | ||
46 | 41 | |||
47 | 42 | typedef void const* TaskId; | ||
48 | 43 | std::future<void> run(std::function<void()> const& task, TaskId id); | ||
49 | 44 | |||
50 | 45 | void shrink(); | ||
51 | 46 | |||
52 | 47 | private: | ||
53 | 48 | BasicThreadPool(BasicThreadPool const&) = delete; | ||
54 | 49 | BasicThreadPool& operator=(BasicThreadPool const&) = delete; | ||
55 | 50 | |||
56 | 51 | std::future<void> run(WorkerThread* t, std::function<void()> const& task, TaskId id); | ||
57 | 52 | WorkerThread *find_thread_by(TaskId id); | ||
58 | 53 | WorkerThread *find_idle_thread(); | ||
59 | 54 | |||
60 | 55 | std::mutex mutex; | ||
61 | 56 | int const min_threads; | ||
62 | 57 | std::vector<std::unique_ptr<WorkerThread>> threads; | ||
63 | 58 | }; | ||
64 | 59 | |||
65 | 60 | } | ||
66 | 61 | } | ||
67 | 62 | |||
68 | 63 | #endif | ||
69 | 0 | 64 | ||
70 | === modified file 'src/server/CMakeLists.txt' | |||
71 | --- src/server/CMakeLists.txt 2014-09-05 03:37:19 +0000 | |||
72 | +++ src/server/CMakeLists.txt 2014-09-06 14:29:21 +0000 | |||
73 | @@ -13,6 +13,7 @@ | |||
74 | 13 | add_subdirectory(scene/) | 13 | add_subdirectory(scene/) |
75 | 14 | add_subdirectory(frontend/) | 14 | add_subdirectory(frontend/) |
76 | 15 | add_subdirectory(shell/) | 15 | add_subdirectory(shell/) |
77 | 16 | add_subdirectory(thread/) | ||
78 | 16 | 17 | ||
79 | 17 | set(PREFIX "${CMAKE_INSTALL_PREFIX}") | 18 | set(PREFIX "${CMAKE_INSTALL_PREFIX}") |
80 | 18 | set(EXEC_PREFIX "${CMAKE_INSTALL_PREFIX}") | 19 | set(EXEC_PREFIX "${CMAKE_INSTALL_PREFIX}") |
81 | @@ -48,6 +49,7 @@ | |||
82 | 48 | $<TARGET_OBJECTS:mirlogger> | 49 | $<TARGET_OBJECTS:mirlogger> |
83 | 49 | $<TARGET_OBJECTS:mirnestedgraphics> | 50 | $<TARGET_OBJECTS:mirnestedgraphics> |
84 | 50 | $<TARGET_OBJECTS:miroffscreengraphics> | 51 | $<TARGET_OBJECTS:miroffscreengraphics> |
85 | 52 | $<TARGET_OBJECTS:mirthread> | ||
86 | 51 | ) | 53 | ) |
87 | 52 | 54 | ||
88 | 53 | set(MIR_SERVER_REFERENCES | 55 | set(MIR_SERVER_REFERENCES |
89 | 54 | 56 | ||
90 | === modified file 'src/server/compositor/multi_threaded_compositor.cpp' | |||
91 | --- src/server/compositor/multi_threaded_compositor.cpp 2014-09-05 03:37:19 +0000 | |||
92 | +++ src/server/compositor/multi_threaded_compositor.cpp 2014-09-06 14:29:21 +0000 | |||
93 | @@ -195,7 +195,8 @@ | |||
94 | 195 | display_buffer_compositor_factory{db_compositor_factory}, | 195 | display_buffer_compositor_factory{db_compositor_factory}, |
95 | 196 | report{compositor_report}, | 196 | report{compositor_report}, |
96 | 197 | state{CompositorState::stopped}, | 197 | state{CompositorState::stopped}, |
98 | 198 | compose_on_start{compose_on_start} | 198 | compose_on_start{compose_on_start}, |
99 | 199 | thread_pool{1} | ||
100 | 199 | { | 200 | { |
101 | 200 | observer = std::make_shared<ms::LegacySceneChangeNotification>( | 201 | observer = std::make_shared<ms::LegacySceneChangeNotification>( |
102 | 201 | [this]() | 202 | [this]() |
103 | @@ -289,10 +290,12 @@ | |||
104 | 289 | auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer, report}; | 290 | auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer, report}; |
105 | 290 | auto thread_functor = std::unique_ptr<mc::CompositingFunctor>(thread_functor_raw); | 291 | auto thread_functor = std::unique_ptr<mc::CompositingFunctor>(thread_functor_raw); |
106 | 291 | 292 | ||
108 | 292 | threads.push_back(std::thread{std::ref(*thread_functor)}); | 293 | futures.push_back(thread_pool.run(std::ref(*thread_functor), &buffer)); |
109 | 293 | thread_functors.push_back(std::move(thread_functor)); | 294 | thread_functors.push_back(std::move(thread_functor)); |
110 | 294 | }); | 295 | }); |
111 | 295 | 296 | ||
112 | 297 | thread_pool.shrink(); | ||
113 | 298 | |||
114 | 296 | state = CompositorState::started; | 299 | state = CompositorState::started; |
115 | 297 | } | 300 | } |
116 | 298 | 301 | ||
117 | @@ -307,11 +310,11 @@ | |||
118 | 307 | for (auto& f : thread_functors) | 310 | for (auto& f : thread_functors) |
119 | 308 | f->stop(); | 311 | f->stop(); |
120 | 309 | 312 | ||
123 | 310 | for (auto& t : threads) | 313 | for (auto& f : futures) |
124 | 311 | t.join(); | 314 | f.wait(); |
125 | 312 | 315 | ||
126 | 313 | thread_functors.clear(); | 316 | thread_functors.clear(); |
128 | 314 | threads.clear(); | 317 | futures.clear(); |
129 | 315 | 318 | ||
130 | 316 | report->stopped(); | 319 | report->stopped(); |
131 | 317 | 320 | ||
132 | 318 | 321 | ||
133 | === modified file 'src/server/compositor/multi_threaded_compositor.h' | |||
134 | --- src/server/compositor/multi_threaded_compositor.h 2014-09-05 03:37:19 +0000 | |||
135 | +++ src/server/compositor/multi_threaded_compositor.h 2014-09-06 14:29:21 +0000 | |||
136 | @@ -20,11 +20,12 @@ | |||
137 | 20 | #define MIR_COMPOSITOR_MULTI_THREADED_COMPOSITOR_H_ | 20 | #define MIR_COMPOSITOR_MULTI_THREADED_COMPOSITOR_H_ |
138 | 21 | 21 | ||
139 | 22 | #include "mir/compositor/compositor.h" | 22 | #include "mir/compositor/compositor.h" |
140 | 23 | #include "mir/thread/basic_thread_pool.h" | ||
141 | 23 | 24 | ||
142 | 24 | #include <mutex> | 25 | #include <mutex> |
143 | 25 | #include <memory> | 26 | #include <memory> |
144 | 26 | #include <vector> | 27 | #include <vector> |
146 | 27 | #include <thread> | 28 | #include <future> |
147 | 28 | 29 | ||
148 | 29 | namespace mir | 30 | namespace mir |
149 | 30 | { | 31 | { |
150 | @@ -76,15 +77,16 @@ | |||
151 | 76 | std::shared_ptr<CompositorReport> const report; | 77 | std::shared_ptr<CompositorReport> const report; |
152 | 77 | 78 | ||
153 | 78 | std::vector<std::unique_ptr<CompositingFunctor>> thread_functors; | 79 | std::vector<std::unique_ptr<CompositingFunctor>> thread_functors; |
155 | 79 | std::vector<std::thread> threads; | 80 | std::vector<std::future<void>> futures; |
156 | 80 | 81 | ||
157 | 81 | std::mutex state_guard; | 82 | std::mutex state_guard; |
158 | 82 | CompositorState state; | 83 | CompositorState state; |
159 | 83 | bool compose_on_start; | 84 | bool compose_on_start; |
160 | 84 | 85 | ||
161 | 85 | void schedule_compositing(int number_composites); | 86 | void schedule_compositing(int number_composites); |
163 | 86 | 87 | ||
164 | 87 | std::shared_ptr<mir::scene::Observer> observer; | 88 | std::shared_ptr<mir::scene::Observer> observer; |
165 | 89 | mir::thread::BasicThreadPool thread_pool; | ||
166 | 88 | }; | 90 | }; |
167 | 89 | 91 | ||
168 | 90 | } | 92 | } |
169 | 91 | 93 | ||
170 | === added directory 'src/server/thread' | |||
171 | === added file 'src/server/thread/CMakeLists.txt' | |||
172 | --- src/server/thread/CMakeLists.txt 1970-01-01 00:00:00 +0000 | |||
173 | +++ src/server/thread/CMakeLists.txt 2014-09-06 14:29:21 +0000 | |||
174 | @@ -0,0 +1,12 @@ | |||
175 | 1 | set( | ||
176 | 2 | MIR_THREAD_SRCS | ||
177 | 3 | |||
178 | 4 | basic_thread_pool.cpp | ||
179 | 5 | ) | ||
180 | 6 | |||
181 | 7 | ADD_LIBRARY( | ||
182 | 8 | mirthread OBJECT | ||
183 | 9 | |||
184 | 10 | ${MIR_THREAD_SRCS} | ||
185 | 11 | ) | ||
186 | 12 | |||
187 | 0 | 13 | ||
188 | === added file 'src/server/thread/basic_thread_pool.cpp' | |||
189 | --- src/server/thread/basic_thread_pool.cpp 1970-01-01 00:00:00 +0000 | |||
190 | +++ src/server/thread/basic_thread_pool.cpp 2014-09-06 14:29:21 +0000 | |||
191 | @@ -0,0 +1,221 @@ | |||
192 | 1 | /* | ||
193 | 2 | * Copyright © 2014 Canonical Ltd. | ||
194 | 3 | * | ||
195 | 4 | * This program is free software: you can redistribute it and/or modify it | ||
196 | 5 | * under the terms of the GNU General Public License version 3, | ||
197 | 6 | * as published by the Free Software Foundation. | ||
198 | 7 | * | ||
199 | 8 | * This program is distributed in the hope that it will be useful, | ||
200 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
201 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
202 | 11 | * GNU General Public License for more details. | ||
203 | 12 | * | ||
204 | 13 | * You should have received a copy of the GNU General Public License | ||
205 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
206 | 15 | * | ||
207 | 16 | * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> | ||
208 | 17 | */ | ||
209 | 18 | |||
210 | 19 | #include "mir/thread/basic_thread_pool.h" | ||
211 | 20 | #include <mir/run_mir.h> | ||
212 | 21 | |||
213 | 22 | #include <deque> | ||
214 | 23 | #include <algorithm> | ||
215 | 24 | #include <condition_variable> | ||
216 | 25 | |||
217 | 26 | namespace mt = mir::thread; | ||
218 | 27 | |||
219 | 28 | namespace | ||
220 | 29 | { | ||
221 | 30 | |||
222 | 31 | class Worker | ||
223 | 32 | { | ||
224 | 33 | public: | ||
225 | 34 | Worker() : exiting{false} | ||
226 | 35 | { | ||
227 | 36 | } | ||
228 | 37 | |||
229 | 38 | ~Worker() | ||
230 | 39 | { | ||
231 | 40 | exit(); | ||
232 | 41 | } | ||
233 | 42 | |||
234 | 43 | void operator()() noexcept | ||
235 | 44 | try | ||
236 | 45 | { | ||
237 | 46 | std::unique_lock<std::mutex> lock{state_mutex}; | ||
238 | 47 | while (!exiting) | ||
239 | 48 | { | ||
240 | 49 | task_available_cv.wait(lock, [&]{ return exiting || !tasks.empty(); }); | ||
241 | 50 | |||
242 | 51 | if (!exiting) | ||
243 | 52 | { | ||
244 | 53 | auto& task = tasks.front(); | ||
245 | 54 | lock.unlock(); | ||
246 | 55 | task(); | ||
247 | 56 | lock.lock(); | ||
248 | 57 | tasks.pop_front(); | ||
249 | 58 | } | ||
250 | 59 | } | ||
251 | 60 | } | ||
252 | 61 | catch(...) | ||
253 | 62 | { | ||
254 | 63 | mir::terminate_with_current_exception(); | ||
255 | 64 | } | ||
256 | 65 | |||
257 | 66 | void queue_task(std::packaged_task<void()> task) | ||
258 | 67 | { | ||
259 | 68 | std::lock_guard<std::mutex> lock{state_mutex}; | ||
260 | 69 | tasks.push_back(std::move(task)); | ||
261 | 70 | task_available_cv.notify_one(); | ||
262 | 71 | } | ||
263 | 72 | |||
264 | 73 | void exit() | ||
265 | 74 | { | ||
266 | 75 | std::lock_guard<std::mutex> lock{state_mutex}; | ||
267 | 76 | exiting = true; | ||
268 | 77 | task_available_cv.notify_one(); | ||
269 | 78 | } | ||
270 | 79 | |||
271 | 80 | bool is_idle() const | ||
272 | 81 | { | ||
273 | 82 | std::lock_guard<std::mutex> lock{state_mutex}; | ||
274 | 83 | return tasks.empty(); | ||
275 | 84 | } | ||
276 | 85 | |||
277 | 86 | private: | ||
278 | 87 | std::deque<std::packaged_task<void()>> tasks; | ||
279 | 88 | bool exiting; | ||
280 | 89 | std::mutex mutable state_mutex; | ||
281 | 90 | std::condition_variable task_available_cv; | ||
282 | 91 | }; | ||
283 | 92 | |||
284 | 93 | } | ||
285 | 94 | |||
286 | 95 | namespace mir | ||
287 | 96 | { | ||
288 | 97 | namespace thread | ||
289 | 98 | { | ||
290 | 99 | class WorkerThread | ||
291 | 100 | { | ||
292 | 101 | public: | ||
293 | 102 | WorkerThread(mt::BasicThreadPool::TaskId an_id) | ||
294 | 103 | : thread{std::ref(worker)}, | ||
295 | 104 | id_{an_id} | ||
296 | 105 | {} | ||
297 | 106 | |||
298 | 107 | ~WorkerThread() | ||
299 | 108 | { | ||
300 | 109 | worker.exit(); | ||
301 | 110 | if (thread.joinable()) | ||
302 | 111 | thread.join(); | ||
303 | 112 | } | ||
304 | 113 | |||
305 | 114 | void queue_task(std::packaged_task<void()> task, mt::BasicThreadPool::TaskId the_id) | ||
306 | 115 | { | ||
307 | 116 | worker.queue_task(std::move(task)); | ||
308 | 117 | id_ = the_id; | ||
309 | 118 | } | ||
310 | 119 | |||
311 | 120 | bool is_idle() const | ||
312 | 121 | { | ||
313 | 122 | return worker.is_idle(); | ||
314 | 123 | } | ||
315 | 124 | |||
316 | 125 | mt::BasicThreadPool::TaskId current_id() const | ||
317 | 126 | { | ||
318 | 127 | return id_; | ||
319 | 128 | } | ||
320 | 129 | |||
321 | 130 | private: | ||
322 | 131 | ::Worker worker; | ||
323 | 132 | std::thread thread; | ||
324 | 133 | mt::BasicThreadPool::TaskId id_; | ||
325 | 134 | }; | ||
326 | 135 | } | ||
327 | 136 | } | ||
328 | 137 | |||
329 | 138 | mt::BasicThreadPool::BasicThreadPool(int min_threads) | ||
330 | 139 | : min_threads{min_threads} | ||
331 | 140 | { | ||
332 | 141 | } | ||
333 | 142 | |||
334 | 143 | mt::BasicThreadPool::~BasicThreadPool() = default; | ||
335 | 144 | |||
336 | 145 | std::future<void> mt::BasicThreadPool::run(std::function<void()> const& task) | ||
337 | 146 | { | ||
338 | 147 | std::lock_guard<decltype(mutex)> lock{mutex}; | ||
339 | 148 | |||
340 | 149 | TaskId const generic_id = nullptr; | ||
341 | 150 | WorkerThread* no_preferred_thread = nullptr; | ||
342 | 151 | return run(no_preferred_thread, task, generic_id); | ||
343 | 152 | } | ||
344 | 153 | |||
345 | 154 | std::future<void> mt::BasicThreadPool::run(std::function<void()> const& task, TaskId id) | ||
346 | 155 | { | ||
347 | 156 | std::lock_guard<decltype(mutex)> lock{mutex}; | ||
348 | 157 | return run(find_thread_by(id), task, id); | ||
349 | 158 | } | ||
350 | 159 | |||
351 | 160 | std::future<void> mt::BasicThreadPool::run(WorkerThread* worker_thread, | ||
352 | 161 | std::function<void()> const& task, TaskId id) | ||
353 | 162 | { | ||
354 | 163 | // No pre-selected thread to execute task, find an idle thread | ||
355 | 164 | if (worker_thread == nullptr) | ||
356 | 165 | worker_thread = find_idle_thread(); | ||
357 | 166 | |||
358 | 167 | if (worker_thread == nullptr) | ||
359 | 168 | { | ||
360 | 169 | // No idle threads available so create a new one | ||
361 | 170 | std::unique_ptr<WorkerThread> new_worker_thread{new WorkerThread{id}}; | ||
362 | 171 | threads.push_back(std::move(new_worker_thread)); | ||
363 | 172 | worker_thread = threads.back().get(); | ||
364 | 173 | } | ||
365 | 174 | |||
366 | 175 | std::packaged_task<void()> a_task{task}; | ||
367 | 176 | auto future = a_task.get_future(); | ||
368 | 177 | worker_thread->queue_task(std::move(a_task), id); | ||
369 | 178 | return future; | ||
370 | 179 | } | ||
371 | 180 | |||
372 | 181 | |||
373 | 182 | void mt::BasicThreadPool::shrink() | ||
374 | 183 | { | ||
375 | 184 | std::lock_guard<decltype(mutex)> lock{mutex}; | ||
376 | 185 | |||
377 | 186 | int max_threads_to_remove = threads.size() - min_threads; | ||
378 | 187 | auto it = std::remove_if(threads.begin(), threads.end(), | ||
379 | 188 | [&max_threads_to_remove](std::unique_ptr<WorkerThread> const& worker_thread) | ||
380 | 189 | { | ||
381 | 190 | bool remove = worker_thread->is_idle() && max_threads_to_remove > 0; | ||
382 | 191 | if (remove) | ||
383 | 192 | max_threads_to_remove--; | ||
384 | 193 | return remove; | ||
385 | 194 | } | ||
386 | 195 | ); | ||
387 | 196 | threads.erase(it, threads.end()); | ||
388 | 197 | } | ||
389 | 198 | |||
390 | 199 | mt::WorkerThread* mt::BasicThreadPool::find_thread_by(TaskId id) | ||
391 | 200 | { | ||
392 | 201 | auto it = std::find_if(threads.begin(), threads.end(), | ||
393 | 202 | [id](std::unique_ptr<WorkerThread> const& worker_thread) | ||
394 | 203 | { | ||
395 | 204 | return worker_thread->current_id() == id; | ||
396 | 205 | } | ||
397 | 206 | ); | ||
398 | 207 | |||
399 | 208 | return it == threads.end() ? nullptr : it->get(); | ||
400 | 209 | } | ||
401 | 210 | |||
402 | 211 | mt::WorkerThread *mt::BasicThreadPool::find_idle_thread() | ||
403 | 212 | { | ||
404 | 213 | auto it = std::find_if(threads.begin(), threads.end(), | ||
405 | 214 | [](std::unique_ptr<WorkerThread> const& worker_thread) | ||
406 | 215 | { | ||
407 | 216 | return worker_thread->is_idle(); | ||
408 | 217 | } | ||
409 | 218 | ); | ||
410 | 219 | |||
411 | 220 | return it == threads.end() ? nullptr : it->get(); | ||
412 | 221 | } | ||
413 | 0 | 222 | ||
414 | === modified file 'tests/unit-tests/CMakeLists.txt' | |||
415 | --- tests/unit-tests/CMakeLists.txt 2014-09-05 03:37:19 +0000 | |||
416 | +++ tests/unit-tests/CMakeLists.txt 2014-09-06 14:29:21 +0000 | |||
417 | @@ -36,6 +36,7 @@ | |||
418 | 36 | add_subdirectory(scene/) | 36 | add_subdirectory(scene/) |
419 | 37 | add_subdirectory(draw/) | 37 | add_subdirectory(draw/) |
420 | 38 | add_subdirectory(examples/) | 38 | add_subdirectory(examples/) |
421 | 39 | add_subdirectory(thread/) | ||
422 | 39 | 40 | ||
423 | 40 | link_directories(${LIBRARY_OUTPUT_PATH}) | 41 | link_directories(${LIBRARY_OUTPUT_PATH}) |
424 | 41 | 42 | ||
425 | 42 | 43 | ||
426 | === added directory 'tests/unit-tests/thread' | |||
427 | === added file 'tests/unit-tests/thread/CMakeLists.txt' | |||
428 | --- tests/unit-tests/thread/CMakeLists.txt 1970-01-01 00:00:00 +0000 | |||
429 | +++ tests/unit-tests/thread/CMakeLists.txt 2014-09-06 14:29:21 +0000 | |||
430 | @@ -0,0 +1,5 @@ | |||
431 | 1 | list(APPEND UNIT_TEST_SOURCES | ||
432 | 2 | ${CMAKE_CURRENT_SOURCE_DIR}/test_basic_thread_pool.cpp | ||
433 | 3 | ) | ||
434 | 4 | |||
435 | 5 | set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES} PARENT_SCOPE) | ||
436 | 0 | 6 | ||
437 | === added file 'tests/unit-tests/thread/test_basic_thread_pool.cpp' | |||
438 | --- tests/unit-tests/thread/test_basic_thread_pool.cpp 1970-01-01 00:00:00 +0000 | |||
439 | +++ tests/unit-tests/thread/test_basic_thread_pool.cpp 2014-09-06 14:29:21 +0000 | |||
440 | @@ -0,0 +1,221 @@ | |||
441 | 1 | /* | ||
442 | 2 | * Copyright © 2014 Canonical Ltd. | ||
443 | 3 | * | ||
444 | 4 | * This program is free software: you can redistribute it and/or modify | ||
445 | 5 | * it under the terms of the GNU General Public License version 3 as | ||
446 | 6 | * published by the Free Software Foundation. | ||
447 | 7 | * | ||
448 | 8 | * This program is distributed in the hope that it will be useful, | ||
449 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
450 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
451 | 11 | * GNU General Public License for more details. | ||
452 | 12 | * | ||
453 | 13 | * You should have received a copy of the GNU General Public License | ||
454 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
455 | 15 | * | ||
456 | 16 | * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> | ||
457 | 17 | */ | ||
458 | 18 | |||
459 | 19 | #include "mir/thread/basic_thread_pool.h" | ||
460 | 20 | |||
461 | 21 | #include "mir/thread_name.h" | ||
462 | 22 | #include "mir_test/current_thread_name.h" | ||
463 | 23 | #include "mir_test/signal.h" | ||
464 | 24 | |||
465 | 25 | #include <memory> | ||
466 | 26 | |||
467 | 27 | #include <gmock/gmock.h> | ||
468 | 28 | #include <gtest/gtest.h> | ||
469 | 29 | |||
470 | 30 | namespace mt = mir::test; | ||
471 | 31 | namespace mth = mir::thread; | ||
472 | 32 | |||
473 | 33 | namespace | ||
474 | 34 | { | ||
475 | 35 | class BasicThreadPool : public testing::Test | ||
476 | 36 | { | ||
477 | 37 | public: | ||
478 | 38 | BasicThreadPool() | ||
479 | 39 | : default_task_id{nullptr}, | ||
480 | 40 | default_num_threads{3}, | ||
481 | 41 | expected_name{"test_thread"} | ||
482 | 42 | { | ||
483 | 43 | } | ||
484 | 44 | protected: | ||
485 | 45 | mth::BasicThreadPool::TaskId default_task_id; | ||
486 | 46 | int default_num_threads; | ||
487 | 47 | std::string expected_name; | ||
488 | 48 | }; | ||
489 | 49 | |||
490 | 50 | class TestTask | ||
491 | 51 | { | ||
492 | 52 | public: | ||
493 | 53 | TestTask() | ||
494 | 54 | : TestTask("") | ||
495 | 55 | { | ||
496 | 56 | } | ||
497 | 57 | |||
498 | 58 | TestTask(std::string name) | ||
499 | 59 | : called{false}, | ||
500 | 60 | block{false}, | ||
501 | 61 | name{name} | ||
502 | 62 | { | ||
503 | 63 | } | ||
504 | 64 | |||
505 | 65 | ~TestTask() | ||
506 | 66 | { | ||
507 | 67 | unblock(); | ||
508 | 68 | } | ||
509 | 69 | |||
510 | 70 | void operator()() noexcept | ||
511 | 71 | { | ||
512 | 72 | called = true; | ||
513 | 73 | if (!name.empty()) | ||
514 | 74 | mir::set_thread_name(name); | ||
515 | 75 | else | ||
516 | 76 | name = mt::current_thread_name(); | ||
517 | 77 | |||
518 | 78 | if (block) | ||
519 | 79 | signal.wait(); | ||
520 | 80 | } | ||
521 | 81 | |||
522 | 82 | std::string thread_name() const | ||
523 | 83 | { | ||
524 | 84 | return name; | ||
525 | 85 | } | ||
526 | 86 | |||
527 | 87 | bool was_called() const | ||
528 | 88 | { | ||
529 | 89 | return called; | ||
530 | 90 | } | ||
531 | 91 | |||
532 | 92 | void block_on_execution() | ||
533 | 93 | { | ||
534 | 94 | block = true; | ||
535 | 95 | } | ||
536 | 96 | |||
537 | 97 | void unblock() | ||
538 | 98 | { | ||
539 | 99 | signal.raise(); | ||
540 | 100 | } | ||
541 | 101 | |||
542 | 102 | private: | ||
543 | 103 | bool called; | ||
544 | 104 | bool block; | ||
545 | 105 | std::string name; | ||
546 | 106 | mt::Signal signal; | ||
547 | 107 | }; | ||
548 | 108 | } | ||
549 | 109 | |||
550 | 110 | TEST_F(BasicThreadPool, executes_given_functor) | ||
551 | 111 | { | ||
552 | 112 | using namespace testing; | ||
553 | 113 | mth::BasicThreadPool p{default_num_threads}; | ||
554 | 114 | |||
555 | 115 | TestTask task; | ||
556 | 116 | auto future = p.run(std::ref(task)); | ||
557 | 117 | future.wait(); | ||
558 | 118 | |||
559 | 119 | EXPECT_TRUE(task.was_called()); | ||
560 | 120 | } | ||
561 | 121 | |||
562 | 122 | TEST_F(BasicThreadPool, executes_on_preferred_thread) | ||
563 | 123 | { | ||
564 | 124 | using namespace testing; | ||
565 | 125 | mth::BasicThreadPool p{default_num_threads}; | ||
566 | 126 | |||
567 | 127 | TestTask task1{expected_name}; | ||
568 | 128 | task1.block_on_execution(); | ||
569 | 129 | auto future1 = p.run(std::ref(task1), default_task_id); | ||
570 | 130 | |||
571 | 131 | // This task should be queued to the thread associated with the given id | ||
572 | 132 | // even when its busy | ||
573 | 133 | TestTask task2; | ||
574 | 134 | auto future2 = p.run(std::ref(task2), default_task_id); | ||
575 | 135 | |||
576 | 136 | task1.unblock(); | ||
577 | 137 | future1.wait(); | ||
578 | 138 | future2.wait(); | ||
579 | 139 | |||
580 | 140 | // Check if the second task executed on the same thread as the first task | ||
581 | 141 | EXPECT_TRUE(task1.was_called()); | ||
582 | 142 | EXPECT_TRUE(task2.was_called()); | ||
583 | 143 | EXPECT_THAT(task2.thread_name(), Eq(expected_name)); | ||
584 | 144 | } | ||
585 | 145 | |||
586 | 146 | TEST_F(BasicThreadPool, recycles_threads) | ||
587 | 147 | { | ||
588 | 148 | using namespace testing; | ||
589 | 149 | mth::BasicThreadPool p{2}; | ||
590 | 150 | |||
591 | 151 | std::string const thread1_name = "thread1"; | ||
592 | 152 | std::string const thread2_name = "thread2"; | ||
593 | 153 | |||
594 | 154 | //Create a couple of blocking tasks, so that we force the pool | ||
595 | 155 | //to assign new threads to each task | ||
596 | 156 | TestTask task1{thread1_name}; | ||
597 | 157 | task1.block_on_execution(); | ||
598 | 158 | auto future1 = p.run(std::ref(task1)); | ||
599 | 159 | |||
600 | 160 | TestTask task2{thread2_name}; | ||
601 | 161 | task2.block_on_execution(); | ||
602 | 162 | auto future2 = p.run(std::ref(task2)); | ||
603 | 163 | |||
604 | 164 | task1.unblock(); | ||
605 | 165 | task2.unblock(); | ||
606 | 166 | future1.wait(); | ||
607 | 167 | future2.wait(); | ||
608 | 168 | |||
609 | 169 | // Now we should have some idle threads, the next task should | ||
610 | 170 | // run in any of the two previously created threads | ||
611 | 171 | TestTask task3; | ||
612 | 172 | auto future = p.run(std::ref(task3)); | ||
613 | 173 | future.wait(); | ||
614 | 174 | |||
615 | 175 | EXPECT_TRUE(task1.was_called()); | ||
616 | 176 | EXPECT_TRUE(task2.was_called()); | ||
617 | 177 | EXPECT_TRUE(task3.was_called()); | ||
618 | 178 | EXPECT_THAT(task3.thread_name(), AnyOf(Eq(thread1_name), Eq(thread2_name))); | ||
619 | 179 | } | ||
620 | 180 | |||
621 | 181 | TEST_F(BasicThreadPool, creates_new_threads) | ||
622 | 182 | { | ||
623 | 183 | using namespace testing; | ||
624 | 184 | mth::BasicThreadPool p{1}; | ||
625 | 185 | |||
626 | 186 | TestTask task1{expected_name}; | ||
627 | 187 | task1.block_on_execution(); | ||
628 | 188 | auto future1 = p.run(std::ref(task1)); | ||
629 | 189 | |||
630 | 190 | TestTask task2; | ||
631 | 191 | auto future2 = p.run(std::ref(task2)); | ||
632 | 192 | |||
633 | 193 | task1.unblock(); | ||
634 | 194 | future1.wait(); | ||
635 | 195 | future2.wait(); | ||
636 | 196 | |||
637 | 197 | EXPECT_TRUE(task1.was_called()); | ||
638 | 198 | EXPECT_TRUE(task2.was_called()); | ||
639 | 199 | EXPECT_THAT(task2.thread_name(), Ne(expected_name)); | ||
640 | 200 | } | ||
641 | 201 | |||
642 | 202 | TEST_F(BasicThreadPool, can_shrink) | ||
643 | 203 | { | ||
644 | 204 | using namespace testing; | ||
645 | 205 | mth::BasicThreadPool p{0}; | ||
646 | 206 | |||
647 | 207 | TestTask task1{expected_name}; | ||
648 | 208 | auto future = p.run(std::ref(task1)); | ||
649 | 209 | future.wait(); | ||
650 | 210 | |||
651 | 211 | // This should delete all threads since we specified a minimum of 0 threads for our pool | ||
652 | 212 | p.shrink(); | ||
653 | 213 | |||
654 | 214 | TestTask task2; | ||
655 | 215 | future = p.run(std::ref(task2)); | ||
656 | 216 | future.wait(); | ||
657 | 217 | |||
658 | 218 | EXPECT_TRUE(task1.was_called()); | ||
659 | 219 | EXPECT_TRUE(task2.was_called()); | ||
660 | 220 | EXPECT_THAT(task2.thread_name(), Ne(expected_name)); | ||
661 | 221 | } |
FAILED: Continuous integration, rev:1895 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- ci/2637/ jenkins. qa.ubuntu. com/job/ mir-android- utopic- i386-build/ 1636 jenkins. qa.ubuntu. com/job/ mir-clang- utopic- amd64-build/ 1642 jenkins. qa.ubuntu. com/job/ mir-mediumtests -utopic- touch/1618/ console jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- amd64-ci/ 1159 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- amd64-ci/ 1159/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- utopic- armhf/555 jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- utopic- armhf/555/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/2675/ console s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 12809
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- team-mir- development- branch- ci/2637/ rebuild
http://