Merge lp:~albaguirre/mir/recycle-compositor-threads into lp:mir
- recycle-compositor-threads
- Merge into development-branch
Status: | Rejected |
---|---|
Rejected by: | Alberto Aguirre |
Proposed branch: | lp:~albaguirre/mir/recycle-compositor-threads |
Merge into: | lp:mir |
Diff against target: |
1605 lines (+996/-137) 16 files modified
include/test/mir_test/signal.h (+1/-0) include/test/mir_test_doubles/mock_compositor_loop.h (+44/-0) include/test/mir_test_doubles/mock_compositor_thread.h (+66/-0) src/server/compositor/CMakeLists.txt (+2/-0) src/server/compositor/compositor_thread.cpp (+141/-0) src/server/compositor/compositor_thread.h (+89/-0) src/server/compositor/compositor_thread_factory.cpp (+32/-0) src/server/compositor/compositor_thread_factory.h (+44/-0) src/server/compositor/default_configuration.cpp (+2/-0) src/server/compositor/multi_threaded_compositor.cpp (+47/-32) src/server/compositor/multi_threaded_compositor.h (+10/-8) tests/integration-tests/test_surface_stack_with_compositor.cpp (+11/-0) tests/mir_test/signal.cpp (+6/-0) tests/unit-tests/compositor/CMakeLists.txt (+1/-0) tests/unit-tests/compositor/test_compositor_thread.cpp (+256/-0) tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+244/-97) |
To merge this branch: | bzr merge lp:~albaguirre/mir/recycle-compositor-threads |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alexandros Frantzis (community) | Needs Information | ||
Daniel van Vugt | Needs Information | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email: mp+232791@code.launchpad.net |
Commit message
Recycle compositor threads whenever possible (LP: #1362841)
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
Daniel van Vugt (vanvugt) wrote : | # |
Sounds like a good-old thread pool pattern [1]. Does it therefore have to be compositor specific? That would seem to limit code reuse.
Daniel van Vugt (vanvugt) wrote : | # |
OK, so two questions right now:
(1) Does the thread pool really need to be compositor-
(2) Given this is a workaround for one specific chipset, isn't "1605 lines (+996/-137)" an overkill? Seems rather large given it's a workaround for just one specific chip. Such workarounds you would hope are temporary, and so should be small so that they might be eliminated in future.
The size of the hammer seems disproportionate to the nail :)
Alexandros Frantzis (afrantzis) wrote : | # |
If it's feasible I would too like to reduce the changes in the Compositor class. Based on Daniel's ThreadPool observation an alternative idea is:
class ThreadPool
{
// When the shared_ptr is released the thread will be marked as available for reuse
// The 'id' is used to enable reusing threads if the ThreadPool implementation supports it.
virtual std::shared_
virtual void clear_unused_
};
// And either dependency on abstract ThreadPool:
MultiThreadedCo
// or it might as well use an object variable (abstract class not necessary) for now if this doesn't hinder testing (probably not a problem for testing since we are currently using threads directly internally anyway):
class MultiThreadedCo
So MultiThreadedCo
"Needs discussion"
Alberto Aguirre (albaguirre) wrote : | # |
> OK, so two questions right now:
>
> (1) Does the thread pool really need to be compositor-
> limit code reuse.
>
> (2) Given this is a workaround for one specific chipset, isn't "1605 lines
> (+996/-137)" an overkill? Seems rather large given it's a workaround for just
> one specific chip. Such workarounds you would hope are temporary, and so
> should be small so that they might be eliminated in future.
>
> The size of the hammer seems disproportionate to the nail :)
1) No it doesn't need to be. I'll make it generic.
2) I don't think so, since a bulk of the MP is adding tests (which we should never see as overkill).
Alberto Aguirre (albaguirre) wrote : | # |
> If it's feasible I would too like to reduce the changes in the Compositor
> class. Based on Daniel's ThreadPool observation an alternative idea is:
>
> class ThreadPool
> {
> // When the shared_ptr is released the thread will be marked as available for
> reuse
> // The 'id' is used to enable reusing threads if the ThreadPool implementation
> supports it.
> virtual std::shared_
> const& functor) = 0;
> virtual void clear_unused_
> };
>
> // And either dependency on abstract ThreadPool:
> MultiThreadedCo
> // or it might as well use an object variable (abstract class not necessary)
> for now if this doesn't hinder testing (probably not a problem for testing
> since we are currently using threads directly internally anyway):
> class MultiThreadedCo
>
> So MultiThreadedCo
> previous behavior is easy: either revert the thread pool changes in MTC, or
> use a simple thread pool implementation that doesn't reuse threads.
>
> "Needs discussion"
Ok, I'll rework.
Daniel van Vugt (vanvugt) wrote : | # |
Just being careful what we wish for...
If you do rework it and find the result is more complex/larger than this branch then maybe we should keep this one. A non-generalised solution like this one is easier to remove later if a driver fix or simpler workaround materializes.
Unmerged revisions
- 1879. By Alberto Aguirre
-
cleanup tests
- 1878. By Alberto Aguirre
-
merge lp:mir/devel
- 1877. By Alberto Aguirre
-
Recycle compositor threads whenever possible
Preview Diff
1 | === modified file 'include/test/mir_test/signal.h' |
2 | --- include/test/mir_test/signal.h 2014-05-22 20:48:20 +0000 |
3 | +++ include/test/mir_test/signal.h 2014-08-30 16:57:50 +0000 |
4 | @@ -37,6 +37,7 @@ |
5 | |
6 | void raise(); |
7 | bool raised(); |
8 | + void reset(); |
9 | |
10 | void wait(); |
11 | template<typename rep, typename period> |
12 | |
13 | === added file 'include/test/mir_test_doubles/mock_compositor_loop.h' |
14 | --- include/test/mir_test_doubles/mock_compositor_loop.h 1970-01-01 00:00:00 +0000 |
15 | +++ include/test/mir_test_doubles/mock_compositor_loop.h 2014-08-30 16:57:50 +0000 |
16 | @@ -0,0 +1,44 @@ |
17 | +/* |
18 | + * Copyright © 2014 Canonical Ltd. |
19 | + * |
20 | + * This program is free software: you can redistribute it and/or modify |
21 | + * it under the terms of the GNU General Public License version 3 as |
22 | + * published by the Free Software Foundation. |
23 | + * |
24 | + * This program is distributed in the hope that it will be useful, |
25 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
26 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
27 | + * GNU General Public License for more details. |
28 | + * |
29 | + * You should have received a copy of the GNU General Public License |
30 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
31 | + * |
32 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
33 | + */ |
34 | + |
35 | +#ifndef MIR_TEST_DOUBLES_MOCK_COMPOSITOR_LOOP_H_ |
36 | +#define MIR_TEST_DOUBLES_MOCK_COMPOSITOR_LOOP_H_ |
37 | + |
38 | +#include "src/server/compositor/compositor_thread.h" |
39 | +#include <gmock/gmock.h> |
40 | + |
41 | +namespace mir |
42 | +{ |
43 | +namespace test |
44 | +{ |
45 | +namespace doubles |
46 | +{ |
47 | + |
48 | +class MockCompositorLoop : public compositor::CompositorLoop |
49 | +{ |
50 | +public: |
51 | + MOCK_METHOD0(run, void()); |
52 | + MOCK_METHOD0(stop, void()); |
53 | + MOCK_METHOD1(schedule_compositing, void(int)); |
54 | +}; |
55 | + |
56 | +} |
57 | +} |
58 | +} |
59 | + |
60 | +#endif |
61 | |
62 | === added file 'include/test/mir_test_doubles/mock_compositor_thread.h' |
63 | --- include/test/mir_test_doubles/mock_compositor_thread.h 1970-01-01 00:00:00 +0000 |
64 | +++ include/test/mir_test_doubles/mock_compositor_thread.h 2014-08-30 16:57:50 +0000 |
65 | @@ -0,0 +1,66 @@ |
66 | +/* |
67 | + * Copyright © 2014 Canonical Ltd. |
68 | + * |
69 | + * This program is free software: you can redistribute it and/or modify |
70 | + * it under the terms of the GNU General Public License version 3 as |
71 | + * published by the Free Software Foundation. |
72 | + * |
73 | + * This program is distributed in the hope that it will be useful, |
74 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
75 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
76 | + * GNU General Public License for more details. |
77 | + * |
78 | + * You should have received a copy of the GNU General Public License |
79 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
80 | + * |
81 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
82 | + */ |
83 | + |
84 | +#ifndef MIR_TEST_DOUBLES_MOCK_COMPOSITOR_THREAD_H_ |
85 | +#define MIR_TEST_DOUBLES_MOCK_COMPOSITOR_THREAD_H_ |
86 | + |
87 | +#include "src/server/compositor/compositor_thread.h" |
88 | +#include <gmock/gmock.h> |
89 | + |
90 | +namespace mir |
91 | +{ |
92 | +namespace test |
93 | +{ |
94 | +namespace doubles |
95 | +{ |
96 | + |
97 | +class MockCompositorThread : public compositor::CompositorThread |
98 | +{ |
99 | +public: |
100 | + MockCompositorThread(std::unique_ptr<compositor::CompositorLoop>& loop) |
101 | + : compositor::CompositorThread(std::move(loop)) |
102 | + { |
103 | + } |
104 | + |
105 | + ~MockCompositorThread() |
106 | + { |
107 | + destroyed(); |
108 | + } |
109 | + |
110 | + void run(std::unique_ptr<compositor::CompositorLoop> loop) override |
111 | + { |
112 | + run_(); |
113 | + compositor::CompositorThread::run(std::move(loop)); |
114 | + } |
115 | + |
116 | + void pause() override |
117 | + { |
118 | + pause_(); |
119 | + compositor::CompositorThread::pause(); |
120 | + } |
121 | + |
122 | + MOCK_METHOD0(run_, void()); |
123 | + MOCK_METHOD0(pause_, void()); |
124 | + MOCK_METHOD0(destroyed, void()); |
125 | +}; |
126 | + |
127 | +} |
128 | +} |
129 | +} |
130 | + |
131 | +#endif |
132 | |
133 | === modified file 'src/server/compositor/CMakeLists.txt' |
134 | --- src/server/compositor/CMakeLists.txt 2014-08-08 02:41:51 +0000 |
135 | +++ src/server/compositor/CMakeLists.txt 2014-08-30 16:57:50 +0000 |
136 | @@ -16,6 +16,8 @@ |
137 | timeout_frame_dropping_policy_factory.cpp |
138 | buffer_queue.cpp |
139 | recently_used_cache.cpp |
140 | + compositor_thread.cpp |
141 | + compositor_thread_factory.cpp |
142 | ) |
143 | |
144 | ADD_LIBRARY( |
145 | |
146 | === added file 'src/server/compositor/compositor_thread.cpp' |
147 | --- src/server/compositor/compositor_thread.cpp 1970-01-01 00:00:00 +0000 |
148 | +++ src/server/compositor/compositor_thread.cpp 2014-08-30 16:57:50 +0000 |
149 | @@ -0,0 +1,141 @@ |
150 | +/* |
151 | + * Copyright © 2014 Canonical Ltd. |
152 | + * |
153 | + * This program is free software: you can redistribute it and/or modify it |
154 | + * under the terms of the GNU General Public License version 3, |
155 | + * as published by the Free Software Foundation. |
156 | + * |
157 | + * This program is distributed in the hope that it will be useful, |
158 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
159 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
160 | + * GNU General Public License for more details. |
161 | + * |
162 | + * You should have received a copy of the GNU General Public License |
163 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
164 | + * |
165 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
166 | + */ |
167 | + |
168 | +#include "compositor_thread.h" |
169 | +#include "mir/run_mir.h" |
170 | +#include "mir/thread_name.h" |
171 | + |
172 | +#include <boost/throw_exception.hpp> |
173 | + |
174 | +namespace mc = mir::compositor; |
175 | + |
176 | +mc::CompositorThread::CompositorThread(std::unique_ptr<mc::CompositorLoop> loop) |
177 | + : compositor_loop{std::move(loop)}, |
178 | + state{CompositorThreadState::running}, |
179 | + thread{&mc::CompositorThread::thread_entry, this} |
180 | +{ |
181 | +} |
182 | + |
183 | +mc::CompositorThread::~CompositorThread() |
184 | +{ |
185 | + stop(); |
186 | + if (thread.joinable()) |
187 | + thread.join(); |
188 | +} |
189 | + |
190 | +void mc::CompositorThread::thread_entry() noexcept // noexcept is important! (LP: #1237332) |
191 | +try |
192 | +{ |
193 | + mir::set_thread_name("Mir/Comp"); |
194 | + |
195 | + std::unique_lock<std::mutex> lock{run_mutex}; |
196 | + while (state != CompositorThreadState::stopping) |
197 | + { |
198 | + run_cv.wait(lock, [&]{ return state != CompositorThreadState::paused; }); |
199 | + |
200 | + if (state == CompositorThreadState::running) |
201 | + { |
202 | + lock.unlock(); |
203 | + compositor_loop->run(); |
204 | + lock.lock(); |
205 | + } |
206 | + |
207 | + if (state == CompositorThreadState::pausing) |
208 | + { |
209 | + state = CompositorThreadState::paused; |
210 | + paused_cv.notify_all(); |
211 | + } |
212 | + } |
213 | +} |
214 | +catch(...) |
215 | +{ |
216 | + std::lock_guard<std::mutex> lock{run_mutex}; |
217 | + state = CompositorThreadState::stopping; |
218 | + compositor_loop = nullptr; |
219 | + paused_cv.notify_all(); |
220 | + |
221 | + mir::terminate_with_current_exception(); |
222 | +} |
223 | + |
224 | +void mc::CompositorThread::run(std::unique_ptr<CompositorLoop> loop) |
225 | +{ |
226 | + std::lock_guard<std::mutex> lock{run_mutex}; |
227 | + |
228 | + if (state == CompositorThreadState::running) |
229 | + { |
230 | + BOOST_THROW_EXCEPTION(std::logic_error("Another compositor loop is already running!")); |
231 | + } |
232 | + |
233 | + compositor_loop = std::move(loop); |
234 | + |
235 | + state = CompositorThreadState::running; |
236 | + run_cv.notify_one(); |
237 | +} |
238 | + |
239 | +void mc::CompositorThread::pause() |
240 | +{ |
241 | + std::unique_lock<std::mutex> lock{run_mutex}; |
242 | + pause(lock); |
243 | +} |
244 | + |
245 | +void mc::CompositorThread::stop() |
246 | +{ |
247 | + std::lock_guard<std::mutex> lock{run_mutex}; |
248 | + |
249 | + state = CompositorThreadState::stopping; |
250 | + |
251 | + if (compositor_loop) |
252 | + compositor_loop->stop(); |
253 | + |
254 | + run_cv.notify_one(); |
255 | +} |
256 | + |
257 | +void mc::CompositorThread::schedule_compositing(int num_frames) |
258 | +{ |
259 | + std::lock_guard<std::mutex> lock{run_mutex}; |
260 | + |
261 | + if (state == CompositorThreadState::running) |
262 | + { |
263 | + compositor_loop->schedule_compositing(num_frames); |
264 | + } |
265 | +} |
266 | + |
267 | +bool mc::CompositorThread::is_running() const |
268 | +{ |
269 | + std::lock_guard<std::mutex> lock{run_mutex}; |
270 | + return state == CompositorThreadState::running; |
271 | +} |
272 | + |
273 | +void mc::CompositorThread::pause(std::unique_lock<std::mutex>& lock) |
274 | +{ |
275 | + if (state != CompositorThreadState::running) |
276 | + { |
277 | + return; |
278 | + } |
279 | + |
280 | + state = mc::CompositorThreadState::pausing; |
281 | + compositor_loop->stop(); |
282 | + run_cv.notify_one(); |
283 | + |
284 | + paused_cv.wait(lock, [&]{ |
285 | + return state == CompositorThreadState::paused || |
286 | + state == CompositorThreadState::stopping; |
287 | + }); |
288 | + |
289 | + compositor_loop = nullptr; |
290 | +} |
291 | |
292 | === added file 'src/server/compositor/compositor_thread.h' |
293 | --- src/server/compositor/compositor_thread.h 1970-01-01 00:00:00 +0000 |
294 | +++ src/server/compositor/compositor_thread.h 2014-08-30 16:57:50 +0000 |
295 | @@ -0,0 +1,89 @@ |
296 | +/* |
297 | + * Copyright © 2014 Canonical Ltd. |
298 | + * |
299 | + * This program is free software: you can redistribute it and/or modify it |
300 | + * under the terms of the GNU General Public License version 3, |
301 | + * as published by the Free Software Foundation. |
302 | + * |
303 | + * This program is distributed in the hope that it will be useful, |
304 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
305 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
306 | + * GNU General Public License for more details. |
307 | + * |
308 | + * You should have received a copy of the GNU General Public License |
309 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
310 | + * |
311 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
312 | + */ |
313 | + |
314 | +#ifndef MIR_COMPOSITOR_THREAD_H_ |
315 | +#define MIR_COMPOSITOR_THREAD_H_ |
316 | + |
317 | +#include "mir/compositor/compositor.h" |
318 | + |
319 | +#include <mutex> |
320 | +#include <memory> |
321 | +#include <thread> |
322 | +#include <condition_variable> |
323 | + |
324 | +namespace mir |
325 | +{ |
326 | +namespace compositor |
327 | +{ |
328 | + |
329 | +class CompositorLoop |
330 | +{ |
331 | +public: |
332 | + CompositorLoop() = default; |
333 | + virtual ~CompositorLoop() = default; |
334 | + |
335 | + virtual void run() = 0; |
336 | + virtual void stop() = 0; |
337 | + virtual void schedule_compositing(int num_frames) = 0; |
338 | + |
339 | +private: |
340 | + CompositorLoop(CompositorLoop const&) = delete; |
341 | + CompositorLoop& operator=(CompositorLoop const&) = delete; |
342 | +}; |
343 | + |
344 | +enum class CompositorThreadState |
345 | +{ |
346 | + stopping, |
347 | + pausing, |
348 | + paused, |
349 | + running |
350 | +}; |
351 | + |
352 | +class CompositorThread |
353 | +{ |
354 | +public: |
355 | + CompositorThread(std::unique_ptr<CompositorLoop> loop); |
356 | + virtual ~CompositorThread(); |
357 | + |
358 | + virtual void run(std::unique_ptr<CompositorLoop> loop); |
359 | + virtual void pause(); |
360 | + virtual void schedule_compositing(int num_frames); |
361 | + |
362 | + virtual bool is_running() const; |
363 | + |
364 | +private: |
365 | + void stop(); |
366 | + void thread_entry() noexcept; |
367 | + void pause(std::unique_lock<std::mutex>& lock); |
368 | + |
369 | + CompositorThread(CompositorThread&) = delete; |
370 | + CompositorThread(CompositorThread const&) = delete; |
371 | + CompositorThread& operator=(CompositorThread const&) = delete; |
372 | + |
373 | + std::unique_ptr<CompositorLoop> compositor_loop; |
374 | + |
375 | + CompositorThreadState state; |
376 | + std::mutex mutable run_mutex ; |
377 | + std::condition_variable run_cv; |
378 | + std::condition_variable paused_cv; |
379 | + std::thread thread; |
380 | +}; |
381 | + |
382 | +} |
383 | +} |
384 | +#endif |
385 | |
386 | === added file 'src/server/compositor/compositor_thread_factory.cpp' |
387 | --- src/server/compositor/compositor_thread_factory.cpp 1970-01-01 00:00:00 +0000 |
388 | +++ src/server/compositor/compositor_thread_factory.cpp 2014-08-30 16:57:50 +0000 |
389 | @@ -0,0 +1,32 @@ |
390 | +/* |
391 | + * Copyright © 2014 Canonical Ltd. |
392 | + * |
393 | + * This program is free software: you can redistribute it and/or modify it |
394 | + * under the terms of the GNU General Public License version 3, |
395 | + * as published by the Free Software Foundation. |
396 | + * |
397 | + * This program is distributed in the hope that it will be useful, |
398 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
399 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
400 | + * GNU General Public License for more details. |
401 | + * |
402 | + * You should have received a copy of the GNU General Public License |
403 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
404 | + * |
405 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
406 | + */ |
407 | + |
408 | +#include "compositor_thread_factory.h" |
409 | +#include "compositor_thread.h" |
410 | + |
411 | +namespace mc = mir::compositor; |
412 | + |
413 | +mc::CompositorThreadFactory::CompositorThreadFactory() = default; |
414 | +mc::CompositorThreadFactory::~CompositorThreadFactory() = default; |
415 | + |
416 | +std::unique_ptr<mc::CompositorThread> |
417 | +mc::CompositorThreadFactory::create_compositor_thread_for(std::unique_ptr<mc::CompositorLoop> loop) |
418 | +{ |
419 | + std::unique_ptr<CompositorThread> compositor_thread{new CompositorThread(std::move(loop))}; |
420 | + return compositor_thread; |
421 | +} |
422 | |
423 | === added file 'src/server/compositor/compositor_thread_factory.h' |
424 | --- src/server/compositor/compositor_thread_factory.h 1970-01-01 00:00:00 +0000 |
425 | +++ src/server/compositor/compositor_thread_factory.h 2014-08-30 16:57:50 +0000 |
426 | @@ -0,0 +1,44 @@ |
427 | +/* |
428 | + * Copyright © 2014 Canonical Ltd. |
429 | + * |
430 | + * This program is free software: you can redistribute it and/or modify it |
431 | + * under the terms of the GNU General Public License version 3, |
432 | + * as published by the Free Software Foundation. |
433 | + * |
434 | + * This program is distributed in the hope that it will be useful, |
435 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
436 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
437 | + * GNU General Public License for more details. |
438 | + * |
439 | + * You should have received a copy of the GNU General Public License |
440 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
441 | + * |
442 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
443 | + */ |
444 | + |
445 | +#ifndef MIR_COMPOSITOR_COMPOSITOR_THREAD_FACTORY_H_ |
446 | +#define MIR_COMPOSITOR_COMPOSITOR_THREAD_FACTORY_H_ |
447 | + |
448 | +#include <memory> |
449 | + |
450 | +namespace mir |
451 | +{ |
452 | +namespace compositor |
453 | +{ |
454 | +class CompositorThread; |
455 | +class CompositorLoop; |
456 | +class CompositorThreadFactory |
457 | +{ |
458 | +public: |
459 | + CompositorThreadFactory(); |
460 | + virtual ~CompositorThreadFactory(); |
461 | + |
462 | + virtual std::unique_ptr<CompositorThread> create_compositor_thread_for( |
463 | + std::unique_ptr<CompositorLoop> loop); |
464 | +}; |
465 | + |
466 | +} |
467 | +} |
468 | + |
469 | + |
470 | +#endif |
471 | |
472 | === modified file 'src/server/compositor/default_configuration.cpp' |
473 | --- src/server/compositor/default_configuration.cpp 2014-08-06 03:10:56 +0000 |
474 | +++ src/server/compositor/default_configuration.cpp 2014-08-30 16:57:50 +0000 |
475 | @@ -20,6 +20,7 @@ |
476 | |
477 | #include "buffer_stream_factory.h" |
478 | #include "default_display_buffer_compositor_factory.h" |
479 | +#include "compositor_thread_factory.h" |
480 | #include "multi_threaded_compositor.h" |
481 | #include "gl_renderer_factory.h" |
482 | #include "compositing_screencast.h" |
483 | @@ -78,6 +79,7 @@ |
484 | the_display(), |
485 | the_scene(), |
486 | the_display_buffer_compositor_factory(), |
487 | + std::make_shared<mc::CompositorThreadFactory>(), |
488 | the_compositor_report(), |
489 | !the_options()->is_set(options::host_socket_opt)); |
490 | }); |
491 | |
492 | === modified file 'src/server/compositor/multi_threaded_compositor.cpp' |
493 | --- src/server/compositor/multi_threaded_compositor.cpp 2014-08-06 03:10:56 +0000 |
494 | +++ src/server/compositor/multi_threaded_compositor.cpp 2014-08-30 16:57:50 +0000 |
495 | @@ -13,10 +13,14 @@ |
496 | * You should have received a copy of the GNU General Public License |
497 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
498 | * |
499 | - * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com> |
500 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
501 | + * Alexandros Frantzis <alexandros.frantzis@canonical.com> |
502 | */ |
503 | |
504 | #include "multi_threaded_compositor.h" |
505 | +#include "compositor_thread.h" |
506 | +#include "compositor_thread_factory.h" |
507 | + |
508 | #include "mir/graphics/display.h" |
509 | #include "mir/graphics/display_buffer.h" |
510 | #include "mir/compositor/display_buffer_compositor.h" |
511 | @@ -85,12 +89,13 @@ |
512 | mg::DisplayBuffer& buffer; |
513 | }; |
514 | |
515 | -class CompositingFunctor |
516 | +class DisplayBufferCompositingLoop : public CompositorLoop |
517 | { |
518 | public: |
519 | - CompositingFunctor(std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory, |
520 | - mg::DisplayBuffer& buffer, |
521 | - std::shared_ptr<CompositorReport> const& report) |
522 | + DisplayBufferCompositingLoop( |
523 | + std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory, |
524 | + mg::DisplayBuffer& buffer, |
525 | + std::shared_ptr<CompositorReport> const& report) |
526 | : display_buffer_compositor_factory{db_compositor_factory}, |
527 | buffer(buffer), |
528 | running{true}, |
529 | @@ -99,11 +104,8 @@ |
530 | { |
531 | } |
532 | |
533 | - void operator()() noexcept // noexcept is important! (LP: #1237332) |
534 | - try |
535 | + void run() |
536 | { |
537 | - mir::set_thread_name("Mir/Comp"); |
538 | - |
539 | /* |
540 | * Make the buffer the current rendering target, and release |
541 | * it when the thread is finished. |
542 | @@ -148,10 +150,6 @@ |
543 | } |
544 | } |
545 | } |
546 | - catch(...) |
547 | - { |
548 | - mir::terminate_with_current_exception(); |
549 | - } |
550 | |
551 | void schedule_compositing(int num_frames) |
552 | { |
553 | @@ -188,11 +186,13 @@ |
554 | std::shared_ptr<mg::Display> const& display, |
555 | std::shared_ptr<mc::Scene> const& scene, |
556 | std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory, |
557 | + std::shared_ptr<CompositorThreadFactory> const& compositor_thread_factory, |
558 | std::shared_ptr<CompositorReport> const& compositor_report, |
559 | bool compose_on_start) |
560 | : display{display}, |
561 | scene{scene}, |
562 | display_buffer_compositor_factory{db_compositor_factory}, |
563 | + compositor_thread_factory{compositor_thread_factory}, |
564 | report{compositor_report}, |
565 | state{CompositorState::stopped}, |
566 | compose_on_start{compose_on_start} |
567 | @@ -218,8 +218,8 @@ |
568 | std::unique_lock<std::mutex> lk(state_guard); |
569 | |
570 | report->scheduled(); |
571 | - for (auto& f : thread_functors) |
572 | - f->schedule_compositing(num); |
573 | + for (auto& t : threads) |
574 | + t.second->schedule_compositing(num); |
575 | } |
576 | |
577 | void mc::MultiThreadedCompositor::start() |
578 | @@ -236,14 +236,15 @@ |
579 | /* To cleanup state if any code below throws */ |
580 | ApplyIfUnwinding cleanup_if_unwinding{ |
581 | [this, &lk]{ |
582 | - destroy_compositing_threads(lk); |
583 | + stop_compositing_threads(lk); |
584 | + threads.clear(); |
585 | }}; |
586 | |
587 | lk.unlock(); |
588 | scene->add_observer(observer); |
589 | lk.lock(); |
590 | |
591 | - create_compositing_threads(); |
592 | + start_compositing_threads(); |
593 | |
594 | /* Optional first render */ |
595 | if (compose_on_start) |
596 | @@ -274,29 +275,49 @@ |
597 | scene->remove_observer(observer); |
598 | lk.lock(); |
599 | |
600 | - destroy_compositing_threads(lk); |
601 | + stop_compositing_threads(lk); |
602 | |
603 | // If the compositor is restarted we've likely got clients blocked |
604 | // so we will need to schedule compositing immediately |
605 | compose_on_start = true; |
606 | } |
607 | |
608 | -void mc::MultiThreadedCompositor::create_compositing_threads() |
609 | +void mc::MultiThreadedCompositor::start_compositing_threads() |
610 | { |
611 | /* Start the display buffer compositing threads */ |
612 | display->for_each_display_buffer([this](mg::DisplayBuffer& buffer) |
613 | { |
614 | - auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer, report}; |
615 | - auto thread_functor = std::unique_ptr<mc::CompositingFunctor>(thread_functor_raw); |
616 | - |
617 | - threads.push_back(std::thread{std::ref(*thread_functor)}); |
618 | - thread_functors.push_back(std::move(thread_functor)); |
619 | + |
620 | + auto loop_raw = new mc::DisplayBufferCompositingLoop{display_buffer_compositor_factory, buffer, report}; |
621 | + auto loop = std::unique_ptr<mc::DisplayBufferCompositingLoop>(loop_raw); |
622 | + |
623 | + auto it = threads.find(&buffer); |
624 | + if (it != threads.end()) |
625 | + { |
626 | + auto& compositor_thread = it->second; |
627 | + compositor_thread->run(std::move(loop)); |
628 | + } |
629 | + else |
630 | + { |
631 | + auto compositor_thread = compositor_thread_factory->create_compositor_thread_for(std::move(loop)); |
632 | + threads[&buffer] = std::move(compositor_thread); |
633 | + } |
634 | }); |
635 | |
636 | + // Some display buffers may not be active anymore - clean up their respective threads. |
637 | + // Note: std::remove_if does not apply to associative containers. |
638 | + for (auto it = threads.begin(), end = threads.end(); it != end;) |
639 | + { |
640 | + if (!it->second->is_running()) |
641 | + it = threads.erase(it); |
642 | + else |
643 | + ++it; |
644 | + } |
645 | + |
646 | state = CompositorState::started; |
647 | } |
648 | |
649 | -void mc::MultiThreadedCompositor::destroy_compositing_threads(std::unique_lock<std::mutex>& lock) |
650 | +void mc::MultiThreadedCompositor::stop_compositing_threads(std::unique_lock<std::mutex>& lock) |
651 | { |
652 | /* Could be called during unwinding, |
653 | * ensure the lock is held before changing state |
654 | @@ -304,14 +325,8 @@ |
655 | if(!lock.owns_lock()) |
656 | lock.lock(); |
657 | |
658 | - for (auto& f : thread_functors) |
659 | - f->stop(); |
660 | - |
661 | for (auto& t : threads) |
662 | - t.join(); |
663 | - |
664 | - thread_functors.clear(); |
665 | - threads.clear(); |
666 | + t.second->pause(); |
667 | |
668 | report->stopped(); |
669 | |
670 | |
671 | === modified file 'src/server/compositor/multi_threaded_compositor.h' |
672 | --- src/server/compositor/multi_threaded_compositor.h 2014-08-06 03:10:56 +0000 |
673 | +++ src/server/compositor/multi_threaded_compositor.h 2014-08-30 16:57:50 +0000 |
674 | @@ -23,7 +23,7 @@ |
675 | |
676 | #include <mutex> |
677 | #include <memory> |
678 | -#include <vector> |
679 | +#include <map> |
680 | #include <thread> |
681 | |
682 | namespace mir |
683 | @@ -31,6 +31,7 @@ |
684 | namespace graphics |
685 | { |
686 | class Display; |
687 | +class DisplayBuffer; |
688 | } |
689 | namespace scene |
690 | { |
691 | @@ -41,8 +42,9 @@ |
692 | { |
693 | |
694 | class DisplayBufferCompositorFactory; |
695 | -class CompositingFunctor; |
696 | +class CompositorThreadFactory; |
697 | class Scene; |
698 | +class CompositorThread; |
699 | class CompositorReport; |
700 | |
701 | enum class CompositorState |
702 | @@ -59,6 +61,7 @@ |
703 | MultiThreadedCompositor(std::shared_ptr<graphics::Display> const& display, |
704 | std::shared_ptr<Scene> const& scene, |
705 | std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory, |
706 | + std::shared_ptr<CompositorThreadFactory> const& compositor_thread_factory, |
707 | std::shared_ptr<CompositorReport> const& compositor_report, |
708 | bool compose_on_start); |
709 | ~MultiThreadedCompositor(); |
710 | @@ -67,23 +70,22 @@ |
711 | void stop(); |
712 | |
713 | private: |
714 | - void create_compositing_threads(); |
715 | - void destroy_compositing_threads(std::unique_lock<std::mutex>& lock); |
716 | + void start_compositing_threads(); |
717 | + void stop_compositing_threads(std::unique_lock<std::mutex>& lock); |
718 | |
719 | std::shared_ptr<graphics::Display> const display; |
720 | std::shared_ptr<Scene> const scene; |
721 | std::shared_ptr<DisplayBufferCompositorFactory> const display_buffer_compositor_factory; |
722 | + std::shared_ptr<CompositorThreadFactory> const compositor_thread_factory; |
723 | std::shared_ptr<CompositorReport> const report; |
724 | |
725 | - std::vector<std::unique_ptr<CompositingFunctor>> thread_functors; |
726 | - std::vector<std::thread> threads; |
727 | - |
728 | + std::map<graphics::DisplayBuffer*, std::unique_ptr<CompositorThread>> threads; |
729 | std::mutex state_guard; |
730 | CompositorState state; |
731 | bool compose_on_start; |
732 | |
733 | void schedule_compositing(int number_composites); |
734 | - |
735 | + |
736 | std::shared_ptr<mir::scene::Observer> observer; |
737 | }; |
738 | |
739 | |
740 | === modified file 'tests/integration-tests/test_surface_stack_with_compositor.cpp' |
741 | --- tests/integration-tests/test_surface_stack_with_compositor.cpp 2014-08-06 03:10:56 +0000 |
742 | +++ tests/integration-tests/test_surface_stack_with_compositor.cpp 2014-08-30 16:57:50 +0000 |
743 | @@ -22,6 +22,7 @@ |
744 | #include "src/server/compositor/gl_renderer_factory.h" |
745 | #include "src/server/scene/basic_surface.h" |
746 | #include "src/server/compositor/default_display_buffer_compositor_factory.h" |
747 | +#include "src/server/compositor/compositor_thread_factory.h" |
748 | #include "src/server/compositor/multi_threaded_compositor.h" |
749 | #include "mir_test/fake_shared.h" |
750 | #include "mir_test_doubles/mock_buffer_stream.h" |
751 | @@ -158,6 +159,7 @@ |
752 | mt::fake_shared(stack), |
753 | mt::fake_shared(renderer_factory), |
754 | null_comp_report}; |
755 | + mc::CompositorThreadFactory comp_thread_factory; |
756 | }; |
757 | } |
758 | |
759 | @@ -167,6 +169,7 @@ |
760 | mt::fake_shared(stub_display), |
761 | mt::fake_shared(stack), |
762 | mt::fake_shared(dbc_factory), |
763 | + mt::fake_shared(comp_thread_factory), |
764 | null_comp_report, true); |
765 | mt_compositor.start(); |
766 | |
767 | @@ -180,6 +183,7 @@ |
768 | mt::fake_shared(stub_display), |
769 | mt::fake_shared(stack), |
770 | mt::fake_shared(dbc_factory), |
771 | + mt::fake_shared(comp_thread_factory), |
772 | null_comp_report, false); |
773 | mt_compositor.start(); |
774 | |
775 | @@ -193,6 +197,7 @@ |
776 | mt::fake_shared(stub_display), |
777 | mt::fake_shared(stack), |
778 | mt::fake_shared(dbc_factory), |
779 | + mt::fake_shared(comp_thread_factory), |
780 | null_comp_report, false); |
781 | mt_compositor.start(); |
782 | |
783 | @@ -214,6 +219,7 @@ |
784 | mt::fake_shared(stub_display), |
785 | mt::fake_shared(stack), |
786 | mt::fake_shared(dbc_factory), |
787 | + mt::fake_shared(comp_thread_factory), |
788 | null_comp_report, false); |
789 | mt_compositor.start(); |
790 | |
791 | @@ -236,6 +242,7 @@ |
792 | mt::fake_shared(stub_display), |
793 | mt::fake_shared(stack), |
794 | mt::fake_shared(dbc_factory), |
795 | + mt::fake_shared(comp_thread_factory), |
796 | null_comp_report, false); |
797 | mt_compositor.start(); |
798 | |
799 | @@ -256,6 +263,7 @@ |
800 | mt::fake_shared(stub_display), |
801 | mt::fake_shared(stack), |
802 | mt::fake_shared(dbc_factory), |
803 | + mt::fake_shared(comp_thread_factory), |
804 | null_comp_report, false); |
805 | mt_compositor.start(); |
806 | |
807 | @@ -280,6 +288,7 @@ |
808 | mt::fake_shared(stub_display), |
809 | mt::fake_shared(stack), |
810 | mt::fake_shared(dbc_factory), |
811 | + mt::fake_shared(comp_thread_factory), |
812 | null_comp_report, false); |
813 | |
814 | mt_compositor.start(); |
815 | @@ -298,6 +307,7 @@ |
816 | mt::fake_shared(stub_display), |
817 | mt::fake_shared(stack), |
818 | mt::fake_shared(dbc_factory), |
819 | + mt::fake_shared(comp_thread_factory), |
820 | null_comp_report, false); |
821 | |
822 | mt_compositor.start(); |
823 | @@ -318,6 +328,7 @@ |
824 | mt::fake_shared(stub_display), |
825 | mt::fake_shared(stack), |
826 | mt::fake_shared(dbc_factory), |
827 | + mt::fake_shared(comp_thread_factory), |
828 | null_comp_report, false); |
829 | |
830 | mt_compositor.start(); |
831 | |
832 | === modified file 'tests/mir_test/signal.cpp' |
833 | --- tests/mir_test/signal.cpp 2014-05-22 20:48:20 +0000 |
834 | +++ tests/mir_test/signal.cpp 2014-08-30 16:57:50 +0000 |
835 | @@ -44,3 +44,9 @@ |
836 | std::unique_lock<decltype(mutex)> lock(mutex); |
837 | cv.wait(lock, [this]() { return signalled; }); |
838 | } |
839 | + |
840 | +void mt::Signal::reset() |
841 | +{ |
842 | + std::unique_lock<decltype(mutex)> lock(mutex); |
843 | + signalled = false; |
844 | +} |
845 | |
846 | === modified file 'tests/unit-tests/compositor/CMakeLists.txt' |
847 | --- tests/unit-tests/compositor/CMakeLists.txt 2014-08-06 03:10:56 +0000 |
848 | +++ tests/unit-tests/compositor/CMakeLists.txt 2014-08-30 16:57:50 +0000 |
849 | @@ -10,6 +10,7 @@ |
850 | ${CMAKE_CURRENT_SOURCE_DIR}/test_screencast_display_buffer.cpp |
851 | ${CMAKE_CURRENT_SOURCE_DIR}/test_compositing_screencast.cpp |
852 | ${CMAKE_CURRENT_SOURCE_DIR}/test_timeout_frame_dropping_policy.cpp |
853 | + ${CMAKE_CURRENT_SOURCE_DIR}/test_compositor_thread.cpp |
854 | ) |
855 | |
856 | set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES} PARENT_SCOPE) |
857 | |
858 | === added file 'tests/unit-tests/compositor/test_compositor_thread.cpp' |
859 | --- tests/unit-tests/compositor/test_compositor_thread.cpp 1970-01-01 00:00:00 +0000 |
860 | +++ tests/unit-tests/compositor/test_compositor_thread.cpp 2014-08-30 16:57:50 +0000 |
861 | @@ -0,0 +1,256 @@ |
862 | +/* |
863 | + * Copyright © 2014 Canonical Ltd. |
864 | + * |
865 | + * This program is free software: you can redistribute it and/or modify |
866 | + * it under the terms of the GNU General Public License version 3 as |
867 | + * published by the Free Software Foundation. |
868 | + * |
869 | + * This program is distributed in the hope that it will be useful, |
870 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
871 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
872 | + * GNU General Public License for more details. |
873 | + * |
874 | + * You should have received a copy of the GNU General Public License |
875 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
876 | + * |
877 | + * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com> |
878 | + */ |
879 | + |
880 | +#include "src/server/compositor/compositor_thread.h" |
881 | + |
882 | +#include "mir_test_doubles/mock_compositor_loop.h" |
883 | +#include "mir_test/current_thread_name.h" |
884 | +#include "mir_test/signal.h" |
885 | +#include "mir/raii.h" |
886 | + |
887 | +#include <memory> |
888 | +#include <chrono> |
889 | +#include <thread> |
890 | +#include <exception> |
891 | +#include <csignal> |
892 | + |
893 | +#include <gmock/gmock.h> |
894 | +#include <gtest/gtest.h> |
895 | + |
896 | +namespace mc = mir::compositor; |
897 | + |
898 | +namespace mt = mir::test; |
899 | +namespace mtd = mir::test::doubles; |
900 | + |
901 | +namespace |
902 | +{ |
903 | + |
904 | +extern "C" { typedef void (*sig_handler)(int); } |
905 | +volatile sig_handler old_signal_handler = nullptr; |
906 | +mt::Signal sigterm_signal; |
907 | + |
908 | +extern "C" void signal_handler(int sig) |
909 | +{ |
910 | + if (sig == SIGTERM) { |
911 | + sigterm_signal.raise(); |
912 | + } |
913 | + |
914 | + signal(sig, old_signal_handler); |
915 | +} |
916 | + |
917 | +extern "C" void ignore_signal(int sig) |
918 | +{ |
919 | + signal(sig, old_signal_handler); |
920 | +} |
921 | + |
922 | +class StubCompositorLoop : public mc::CompositorLoop |
923 | +{ |
924 | +public: |
925 | + void run() override {} |
926 | + void stop() override {} |
927 | + void schedule_compositing(int) override {} |
928 | +}; |
929 | + |
930 | +class CompositorThread : public testing::Test |
931 | +{ |
932 | +public: |
933 | + CompositorThread() |
934 | + { |
935 | + new_mock_loop(); |
936 | + } |
937 | + |
938 | + void new_mock_loop() |
939 | + { |
940 | + std::unique_ptr<mtd::MockCompositorLoop> loop{new testing::NiceMock<mtd::MockCompositorLoop>()}; |
941 | + mock_loop = std::move(loop); |
942 | + ON_CALL(*mock_loop, run()) |
943 | + .WillByDefault(InvokeWithoutArgs(this, &CompositorThread::raise_run_signal)); |
944 | + run_signal.reset(); |
945 | + } |
946 | + |
947 | + void raise_run_signal() |
948 | + { |
949 | + run_signal.raise(); |
950 | + std::this_thread::yield(); |
951 | + } |
952 | + |
953 | + std::unique_ptr<mtd::MockCompositorLoop> mock_loop; |
954 | + mt::Signal run_signal; |
955 | +}; |
956 | +} |
957 | + |
958 | +TEST_F(CompositorThread, runs_immediately_on_construction) |
959 | +{ |
960 | + using namespace testing; |
961 | + |
962 | + EXPECT_CALL(*mock_loop, run()) |
963 | + .Times(AnyNumber()); |
964 | + |
965 | + mc::CompositorThread thread{std::move(mock_loop)}; |
966 | + |
967 | + run_signal.wait_for(std::chrono::seconds{1}); |
968 | + EXPECT_TRUE(run_signal.raised()); |
969 | +} |
970 | + |
971 | +TEST_F(CompositorThread, can_pause) |
972 | +{ |
973 | + using namespace testing; |
974 | + |
975 | + EXPECT_CALL(*mock_loop, stop()); |
976 | + |
977 | + mc::CompositorThread thread{std::move(mock_loop)}; |
978 | + |
979 | + thread.pause(); |
980 | + |
981 | + EXPECT_FALSE(thread.is_running()); |
982 | +} |
983 | + |
984 | +TEST_F(CompositorThread, only_pauses_when_thread_is_already_running) |
985 | +{ |
986 | + using namespace testing; |
987 | + |
988 | + EXPECT_CALL(*mock_loop, stop()) |
989 | + .Times(1); |
990 | + |
991 | + mc::CompositorThread thread{std::move(mock_loop)}; |
992 | + |
993 | + thread.pause(); |
994 | + |
995 | + EXPECT_FALSE(thread.is_running()); |
996 | + |
997 | + thread.pause(); |
998 | +} |
999 | + |
1000 | +TEST_F(CompositorThread, destructor_stops_loop) |
1001 | +{ |
1002 | + using namespace testing; |
1003 | + |
1004 | + EXPECT_CALL(*mock_loop, stop()); |
1005 | + mc::CompositorThread thread{std::move(mock_loop)}; |
1006 | +} |
1007 | + |
1008 | +TEST_F(CompositorThread, raises_sigterm_on_thread_exception) |
1009 | +{ |
1010 | + using namespace testing; |
1011 | + |
1012 | + sigterm_signal.reset(); |
1013 | + |
1014 | + auto const raii = mir::raii::paired_calls( |
1015 | + [&]{ old_signal_handler = signal(SIGTERM, signal_handler); }, |
1016 | + [&]{ signal(SIGTERM, old_signal_handler); }); |
1017 | + |
1018 | + EXPECT_CALL(*mock_loop, run()) |
1019 | + .WillOnce(Throw(std::logic_error(""))); |
1020 | + |
1021 | + mc::CompositorThread thread{std::move(mock_loop)}; |
1022 | + |
1023 | + sigterm_signal.wait_for(std::chrono::seconds{1}); |
1024 | + EXPECT_TRUE(sigterm_signal.raised()); |
1025 | +} |
1026 | + |
1027 | +TEST_F(CompositorThread, pause_does_not_block_after_thread_exception) |
1028 | +{ |
1029 | + using namespace testing; |
1030 | + |
1031 | + auto const raii = mir::raii::paired_calls( |
1032 | + [&]{ old_signal_handler = signal(SIGTERM, ignore_signal); }, |
1033 | + [&]{ signal(SIGTERM, old_signal_handler); }); |
1034 | + |
1035 | + auto throw_on_run = [this] { |
1036 | + run_signal.raise(); |
1037 | + throw std::runtime_error(""); |
1038 | + }; |
1039 | + EXPECT_CALL(*mock_loop, run()) |
1040 | + .WillOnce(Invoke(throw_on_run)); |
1041 | + |
1042 | + mc::CompositorThread thread{std::move(mock_loop)}; |
1043 | + |
1044 | + run_signal.wait_for(std::chrono::seconds{1}); |
1045 | + EXPECT_TRUE(run_signal.raised()); |
1046 | + |
1047 | + thread.pause(); |
1048 | +} |
1049 | + |
1050 | +TEST_F(CompositorThread, names_itself) |
1051 | +{ |
1052 | + using namespace testing; |
1053 | + |
1054 | + std::string thread_name; |
1055 | + auto check_thread_name = [&]{ |
1056 | + thread_name = mt::current_thread_name(); |
1057 | + run_signal.raise(); |
1058 | + }; |
1059 | + |
1060 | + EXPECT_CALL(*mock_loop, run()) |
1061 | + .WillRepeatedly(InvokeWithoutArgs(check_thread_name)); |
1062 | + |
1063 | + mc::CompositorThread thread{std::move(mock_loop)}; |
1064 | + |
1065 | + run_signal.wait_for(std::chrono::seconds{1}); |
1066 | + |
1067 | + EXPECT_TRUE(run_signal.raised()); |
1068 | + EXPECT_THAT(thread_name, Eq("Mir/Comp")); |
1069 | +} |
1070 | + |
1071 | +TEST_F(CompositorThread, makes_loop_schedule_compositing) |
1072 | +{ |
1073 | + using namespace testing; |
1074 | + |
1075 | + EXPECT_CALL(*mock_loop, schedule_compositing(_)); |
1076 | + |
1077 | + mc::CompositorThread thread{std::move(mock_loop)}; |
1078 | + int const arbitrary_num_frames = 1; |
1079 | + thread.schedule_compositing(arbitrary_num_frames); |
1080 | +} |
1081 | + |
1082 | +TEST_F(CompositorThread, throws_when_run_invoked_before_pause) |
1083 | +{ |
1084 | + using namespace testing; |
1085 | + |
1086 | + mc::CompositorThread thread{std::move(mock_loop)}; |
1087 | + |
1088 | + new_mock_loop(); |
1089 | + |
1090 | + EXPECT_THROW(thread.run(std::move(mock_loop)), std::logic_error); |
1091 | +} |
1092 | + |
1093 | +TEST_F(CompositorThread, can_run_another_loop) |
1094 | +{ |
1095 | + using namespace testing; |
1096 | + |
1097 | + EXPECT_CALL(*mock_loop, run()) |
1098 | + .Times(AnyNumber()); |
1099 | + |
1100 | + mc::CompositorThread thread{std::move(mock_loop)}; |
1101 | + |
1102 | + run_signal.wait_for(std::chrono::seconds{1}); |
1103 | + EXPECT_TRUE(run_signal.raised()); |
1104 | + |
1105 | + new_mock_loop(); |
1106 | + |
1107 | + EXPECT_FALSE(run_signal.raised()); |
1108 | + |
1109 | + EXPECT_CALL(*mock_loop, run()) |
1110 | + .Times(AnyNumber()); |
1111 | + |
1112 | + thread.pause(); |
1113 | + thread.run(std::move(mock_loop)); |
1114 | + |
1115 | + run_signal.wait_for(std::chrono::seconds{1}); |
1116 | + EXPECT_TRUE(run_signal.raised()); |
1117 | +} |
1118 | |
1119 | === modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp' |
1120 | --- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-08-06 03:10:56 +0000 |
1121 | +++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-08-30 16:57:50 +0000 |
1122 | @@ -1,5 +1,5 @@ |
1123 | /* |
1124 | - * Copyright © 2013 Canonical Ltd. |
1125 | + * Copyright © 2013-2014 Canonical Ltd. |
1126 | * |
1127 | * This program is free software: you can redistribute it and/or modify |
1128 | * it under the terms of the GNU General Public License version 3 as |
1129 | @@ -13,11 +13,15 @@ |
1130 | * You should have received a copy of the GNU General Public License |
1131 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1132 | * |
1133 | - * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com> |
1134 | + * Authored by: |
1135 | + * Alberto Aguirre <alberto.aguirre@canonical.com> |
1136 | + * Alexandros Frantzis <alexandros.frantzis@canonical.com> |
1137 | */ |
1138 | |
1139 | #include "src/server/compositor/multi_threaded_compositor.h" |
1140 | #include "src/server/report/null_report_factory.h" |
1141 | +#include "src/server/compositor/compositor_thread.h" |
1142 | +#include "src/server/compositor/compositor_thread_factory.h" |
1143 | |
1144 | #include "mir/compositor/display_buffer_compositor.h" |
1145 | #include "mir/compositor/scene.h" |
1146 | @@ -31,6 +35,7 @@ |
1147 | #include "mir_test_doubles/mock_compositor_report.h" |
1148 | #include "mir_test_doubles/mock_scene.h" |
1149 | #include "mir_test_doubles/stub_scene.h" |
1150 | +#include "mir_test_doubles/mock_compositor_thread.h" |
1151 | |
1152 | #include <boost/throw_exception.hpp> |
1153 | |
1154 | @@ -42,6 +47,7 @@ |
1155 | |
1156 | #include <gmock/gmock.h> |
1157 | #include <gtest/gtest.h> |
1158 | +#include "mir_test/gmock_fixes.h" |
1159 | |
1160 | namespace mc = mir::compositor; |
1161 | namespace mg = mir::graphics; |
1162 | @@ -65,6 +71,12 @@ |
1163 | f(db); |
1164 | } |
1165 | |
1166 | + void new_buffers(unsigned int nbuffers) |
1167 | + { |
1168 | + buffers.clear(); |
1169 | + buffers = std::vector<mtd::NullDisplayBuffer>{nbuffers}; |
1170 | + } |
1171 | + |
1172 | private: |
1173 | std::vector<mtd::NullDisplayBuffer> buffers; |
1174 | }; |
1175 | @@ -114,7 +126,7 @@ |
1176 | { |
1177 | { |
1178 | std::lock_guard<std::mutex> lock{observer_mutex}; |
1179 | - |
1180 | + |
1181 | // Any old event will do. |
1182 | if (observer) |
1183 | observer->surfaces_reordered(); |
1184 | @@ -315,34 +327,66 @@ |
1185 | } |
1186 | }; |
1187 | |
1188 | -class ThreadNameDisplayBufferCompositorFactory : public mc::DisplayBufferCompositorFactory |
1189 | -{ |
1190 | -public: |
1191 | - std::unique_ptr<mc::DisplayBufferCompositor> create_compositor_for(mg::DisplayBuffer&) |
1192 | - { |
1193 | - auto raw = new RecordingDisplayBufferCompositor{ |
1194 | - [this]() |
1195 | - { |
1196 | - std::lock_guard<std::mutex> lock{thread_names_mutex}; |
1197 | - thread_names.emplace_back(mt::current_thread_name()); |
1198 | - }}; |
1199 | - return std::unique_ptr<RecordingDisplayBufferCompositor>(raw); |
1200 | - } |
1201 | - |
1202 | - size_t num_thread_names_gathered() |
1203 | - { |
1204 | - std::lock_guard<std::mutex> lock{thread_names_mutex}; |
1205 | - return thread_names.size(); |
1206 | - } |
1207 | - |
1208 | - std::mutex thread_names_mutex; |
1209 | - std::vector<std::string> thread_names; |
1210 | -}; |
1211 | - |
1212 | auto const null_report = mr::null_compositor_report(); |
1213 | unsigned int const composites_per_update{1}; |
1214 | } |
1215 | |
1216 | +class CompositorThreadMockFactory : public mc::CompositorThreadFactory |
1217 | +{ |
1218 | +public: |
1219 | + CompositorThreadMockFactory() : CompositorThreadMockFactory( |
1220 | + [](mtd::MockCompositorThread* t) { |
1221 | + //Can't use NiceMock on MockCompositorThread |
1222 | + using namespace testing; |
1223 | + EXPECT_CALL(*t, run_()).Times(AnyNumber()); |
1224 | + EXPECT_CALL(*t, pause_()).Times(AnyNumber()); |
1225 | + EXPECT_CALL(*t, destroyed()); |
1226 | + }) |
1227 | + { |
1228 | + } |
1229 | + |
1230 | + CompositorThreadMockFactory(std::function<void(mtd::MockCompositorThread*)> notify_on_create) |
1231 | + : on_create{notify_on_create}, |
1232 | + num_thread_deaths_{0} |
1233 | + { |
1234 | + using namespace testing; |
1235 | + ON_CALL(*this, create_thread(_)) |
1236 | + .WillByDefault(Invoke(this, &CompositorThreadMockFactory::create_thread_)); |
1237 | + } |
1238 | + |
1239 | + void record_thread_death() |
1240 | + { |
1241 | + num_thread_deaths_++; |
1242 | + } |
1243 | + |
1244 | + int num_thread_deaths() const |
1245 | + { |
1246 | + return num_thread_deaths_; |
1247 | + } |
1248 | + |
1249 | + std::unique_ptr<mc::CompositorThread> create_compositor_thread_for(std::unique_ptr<mc::CompositorLoop> loop) override |
1250 | + { |
1251 | + return create_thread(loop); |
1252 | + } |
1253 | + |
1254 | + std::unique_ptr<mc::CompositorThread> create_thread_(std::unique_ptr<mc::CompositorLoop>& loop) |
1255 | + { |
1256 | + using namespace testing; |
1257 | + auto mock_thread = new mtd::MockCompositorThread(loop); |
1258 | + ON_CALL(*mock_thread, destroyed()) |
1259 | + .WillByDefault(Invoke(this, &CompositorThreadMockFactory::record_thread_death)); |
1260 | + |
1261 | + if (on_create) |
1262 | + on_create(mock_thread); |
1263 | + return std::unique_ptr<mc::CompositorThread>{mock_thread}; |
1264 | + } |
1265 | + |
1266 | + MOCK_METHOD1(create_thread, std::unique_ptr<mc::CompositorThread>(std::unique_ptr<mc::CompositorLoop>&)); |
1267 | +private: |
1268 | + std::function<void(mtd::MockCompositorThread*)> on_create; |
1269 | + int num_thread_deaths_; |
1270 | +}; |
1271 | + |
1272 | TEST(MultiThreadedCompositor, compositing_happens_in_different_threads) |
1273 | { |
1274 | using namespace testing; |
1275 | @@ -352,7 +396,8 @@ |
1276 | auto display = std::make_shared<StubDisplay>(nbuffers); |
1277 | auto scene = std::make_shared<StubScene>(); |
1278 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
1279 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true}; |
1280 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1281 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true}; |
1282 | |
1283 | compositor.start(); |
1284 | |
1285 | @@ -373,9 +418,11 @@ |
1286 | auto scene = std::make_shared<StubScene>(); |
1287 | auto db_compositor_factory = |
1288 | std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
1289 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1290 | auto mock_report = std::make_shared<mtd::MockCompositorReport>(); |
1291 | mc::MultiThreadedCompositor compositor{display, scene, |
1292 | db_compositor_factory, |
1293 | + thread_factory, |
1294 | mock_report, |
1295 | true}; |
1296 | |
1297 | @@ -427,7 +474,8 @@ |
1298 | auto display = std::make_shared<StubDisplay>(nbuffers); |
1299 | auto scene = std::make_shared<StubScene>(); |
1300 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
1301 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true}; |
1302 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1303 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true}; |
1304 | |
1305 | // Verify we're actually starting at zero frames |
1306 | EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); |
1307 | @@ -487,7 +535,8 @@ |
1308 | auto display = std::make_shared<StubDisplay>(nbuffers); |
1309 | auto scene = std::make_shared<StubScene>(); |
1310 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
1311 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, false}; |
1312 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1313 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, false}; |
1314 | |
1315 | // Verify we're actually starting at zero frames |
1316 | ASSERT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0)); |
1317 | @@ -510,7 +559,8 @@ |
1318 | auto display = std::make_shared<StubDisplay>(nbuffers); |
1319 | auto scene = std::make_shared<StubScene>(); |
1320 | auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
1321 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, false}; |
1322 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1323 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, false}; |
1324 | |
1325 | compositor.start(); |
1326 | |
1327 | @@ -545,7 +595,8 @@ |
1328 | auto display = std::make_shared<StubDisplay>(nbuffers); |
1329 | auto scene = std::make_shared<StubScene>(); |
1330 | auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
1331 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true}; |
1332 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1333 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true}; |
1334 | |
1335 | compositor.start(); |
1336 | |
1337 | @@ -563,8 +614,10 @@ |
1338 | |
1339 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); |
1340 | auto scene = std::make_shared<StubScene>(); |
1341 | - auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>(); |
1342 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true}; |
1343 | + auto db_compositor_factory = |
1344 | + std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
1345 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1346 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true}; |
1347 | |
1348 | display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf) |
1349 | { |
1350 | @@ -575,6 +628,10 @@ |
1351 | }); |
1352 | |
1353 | compositor.start(); |
1354 | + |
1355 | + while (!db_compositor_factory->enough_records_gathered(nbuffers, nbuffers)) |
1356 | + scene->emit_change_event(); |
1357 | + |
1358 | compositor.stop(); |
1359 | } |
1360 | |
1361 | @@ -586,6 +643,7 @@ |
1362 | auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); |
1363 | auto mock_scene = std::make_shared<mtd::MockScene>(); |
1364 | auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>(); |
1365 | + auto thread_factory = std::make_shared<mc::CompositorThreadFactory>(); |
1366 | auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>(); |
1367 | |
1368 | EXPECT_CALL(*mock_report, started()) |
1369 | @@ -600,7 +658,7 @@ |
1370 | .Times(AtLeast(0)) |
1371 | .WillRepeatedly(Return(mc::SceneElementSequence{})); |
1372 | |
1373 | - mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, mock_report, true}; |
1374 | + mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, thread_factory, mock_report, true}; |
1375 | |
1376 | compositor.start(); |
1377 | compositor.start(); |
1378 | @@ -608,76 +666,165 @@ |
1379 | compositor.stop(); |
1380 | } |
1381 | |
1382 | -TEST(MultiThreadedCompositor, cleans_up_after_throw_in_start) |
1383 | +TEST(MultiThreadedCompositor, no_threads_created_when_adding_scene_observer_throws) |
1384 | { |
1385 | + using namespace testing; |
1386 | + |
1387 | unsigned int const nbuffers{3}; |
1388 | - |
1389 | - auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); |
1390 | + auto display = std::make_shared<StubDisplay>(nbuffers); |
1391 | auto scene = std::make_shared<StubScene>(); |
1392 | - auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>(); |
1393 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true}; |
1394 | + auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
1395 | + auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(); |
1396 | + |
1397 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true}; |
1398 | + |
1399 | + EXPECT_CALL(*mock_thread_factory, create_thread(_)) |
1400 | + .Times(0); |
1401 | |
1402 | scene->throw_on_add_observer(true); |
1403 | - |
1404 | EXPECT_THROW(compositor.start(), std::runtime_error); |
1405 | - |
1406 | scene->throw_on_add_observer(false); |
1407 | - |
1408 | - /* No point in running the rest of the test if it throws again */ |
1409 | - ASSERT_NO_THROW(compositor.start()); |
1410 | - |
1411 | - /* The minimum number of records here should be nbuffers *2, since we are checking for |
1412 | - * presence of at least one additional rogue compositor thread per display buffer |
1413 | - * However to avoid timing considerations like one good thread compositing the display buffer |
1414 | - * twice before the rogue thread gets a chance to, an arbitrary number of records are gathered |
1415 | - */ |
1416 | - unsigned int min_number_of_records = 100; |
1417 | - |
1418 | - /* Timeout here in case the exception from setting the scene callback put the compositor |
1419 | - * in a bad state that did not allow it to composite (hence no records gathered) |
1420 | - */ |
1421 | - auto time_out = std::chrono::steady_clock::now() + std::chrono::seconds(1); |
1422 | - while (!db_compositor_factory->enough_records_gathered(nbuffers, min_number_of_records) && |
1423 | - std::chrono::steady_clock::now() <= time_out) |
1424 | +} |
1425 | + |
1426 | +TEST(MultiThreadedCompositor, cleans_up_allocated_threads_after_throw_during_next_thread_creation) |
1427 | +{ |
1428 | + using namespace testing; |
1429 | + |
1430 | + unsigned int const nbuffers{3}; |
1431 | + auto display = std::make_shared<StubDisplay>(nbuffers); |
1432 | + auto scene = std::make_shared<StubScene>(); |
1433 | + auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
1434 | + |
1435 | + auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) { |
1436 | + EXPECT_CALL(*t, pause_()); |
1437 | + EXPECT_CALL(*t, destroyed()); |
1438 | + }; |
1439 | + auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations); |
1440 | + |
1441 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true}; |
1442 | + |
1443 | + EXPECT_CALL(*mock_thread_factory, create_thread(_)) |
1444 | + .WillOnce(Invoke(mock_thread_factory.get(), &CompositorThreadMockFactory::create_thread_)) |
1445 | + .WillOnce(Invoke(mock_thread_factory.get(), &CompositorThreadMockFactory::create_thread_)) |
1446 | + .WillOnce(Throw(std::runtime_error(""))); |
1447 | + |
1448 | + EXPECT_THROW(compositor.start(), std::runtime_error); |
1449 | + EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(2)); |
1450 | +} |
1451 | + |
1452 | +TEST(MultiThreadedCompositor, recycles_threads) |
1453 | +{ |
1454 | + using namespace testing; |
1455 | + |
1456 | + unsigned int const nbuffers{3}; |
1457 | + auto display = std::make_shared<StubDisplay>(nbuffers); |
1458 | + auto scene = std::make_shared<StubScene>(); |
1459 | + auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
1460 | + |
1461 | + auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) { |
1462 | + //Run is only called when a thread has been recycled |
1463 | + EXPECT_CALL(*t, run_()); |
1464 | + EXPECT_CALL(*t, destroyed()); |
1465 | + EXPECT_CALL(*t, pause_()) |
1466 | + .Times(2); |
1467 | + }; |
1468 | + auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations); |
1469 | + |
1470 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true}; |
1471 | + |
1472 | + EXPECT_CALL(*mock_thread_factory, create_thread(_)) |
1473 | + .Times(nbuffers); |
1474 | + |
1475 | + compositor.start(); |
1476 | + compositor.stop(); |
1477 | + compositor.start(); |
1478 | + compositor.stop(); |
1479 | + |
1480 | + EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(0)); |
1481 | +} |
1482 | + |
1483 | +TEST(MultiThreadedCompositor, allocates_new_threads_for_new_display_buffers) |
1484 | +{ |
1485 | + using namespace testing; |
1486 | + |
1487 | + unsigned int const nbuffers{3}; |
1488 | + auto display = std::make_shared<StubDisplay>(nbuffers); |
1489 | + auto scene = std::make_shared<StubScene>(); |
1490 | + auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
1491 | + auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(); |
1492 | + |
1493 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true}; |
1494 | + |
1495 | + EXPECT_CALL(*mock_thread_factory, create_thread(_)) |
1496 | + .Times(nbuffers*2); |
1497 | + |
1498 | + compositor.start(); |
1499 | + compositor.stop(); |
1500 | + |
1501 | + display->new_buffers(nbuffers); |
1502 | + |
1503 | + compositor.start(); |
1504 | + compositor.stop(); |
1505 | +} |
1506 | + |
1507 | +TEST(MultiThreadedCompositor, cleans_up_threads_for_unused_display_buffers_at_next_start) |
1508 | +{ |
1509 | + using namespace testing; |
1510 | + |
1511 | + unsigned int const nbuffers{3}; |
1512 | + |
1513 | + auto display = std::make_shared<StubDisplay>(nbuffers); |
1514 | + auto scene = std::make_shared<StubScene>(); |
1515 | + auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
1516 | + |
1517 | + auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) { |
1518 | + EXPECT_CALL(*t, run_()) |
1519 | + .Times(0); |
1520 | + EXPECT_CALL(*t, pause_()); |
1521 | + EXPECT_CALL(*t, destroyed()); |
1522 | + }; |
1523 | + auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations); |
1524 | + |
1525 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true}; |
1526 | + |
1527 | + EXPECT_CALL(*mock_thread_factory, create_thread(_)) |
1528 | + .Times(nbuffers*2); |
1529 | + |
1530 | + compositor.start(); |
1531 | + compositor.stop(); |
1532 | + |
1533 | + display->new_buffers(nbuffers); |
1534 | + |
1535 | + compositor.start(); |
1536 | + |
1537 | + EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(nbuffers)); |
1538 | +} |
1539 | + |
1540 | +TEST(MultiThreadedCompositor, destructor_cleans_up_threads) |
1541 | +{ |
1542 | + using namespace testing; |
1543 | + |
1544 | + unsigned int const nbuffers{3}; |
1545 | + |
1546 | + auto display = std::make_shared<StubDisplay>(nbuffers); |
1547 | + auto scene = std::make_shared<StubScene>(); |
1548 | + auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene); |
1549 | + |
1550 | + auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) { |
1551 | + EXPECT_CALL(*t, run_()) |
1552 | + .Times(0); |
1553 | + EXPECT_CALL(*t, pause_()); |
1554 | + EXPECT_CALL(*t, destroyed()); |
1555 | + }; |
1556 | + auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations); |
1557 | + |
1558 | + EXPECT_CALL(*mock_thread_factory, create_thread(_)) |
1559 | + .Times(nbuffers); |
1560 | + |
1561 | { |
1562 | - scene->emit_change_event(); |
1563 | - std::this_thread::yield(); |
1564 | + mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true}; |
1565 | + compositor.start(); |
1566 | } |
1567 | |
1568 | - /* Check expectation in case a timeout happened */ |
1569 | - EXPECT_TRUE(db_compositor_factory->enough_records_gathered(nbuffers, min_number_of_records)); |
1570 | - |
1571 | - compositor.stop(); |
1572 | - |
1573 | - /* Only one thread should be rendering each display buffer |
1574 | - * If the compositor failed to cleanup correctly more than one thread could be |
1575 | - * compositing the same display buffer |
1576 | - */ |
1577 | - EXPECT_TRUE(db_compositor_factory->each_buffer_rendered_in_single_thread()); |
1578 | -} |
1579 | - |
1580 | -TEST(MultiThreadedCompositor, names_compositor_threads) |
1581 | -{ |
1582 | - using namespace testing; |
1583 | - |
1584 | - unsigned int const nbuffers{3}; |
1585 | - |
1586 | - auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers); |
1587 | - auto scene = std::make_shared<StubScene>(); |
1588 | - auto db_compositor_factory = std::make_shared<ThreadNameDisplayBufferCompositorFactory>(); |
1589 | - mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true}; |
1590 | - |
1591 | - compositor.start(); |
1592 | - |
1593 | - unsigned int const min_number_of_thread_names = 10; |
1594 | - |
1595 | - while (db_compositor_factory->num_thread_names_gathered() < min_number_of_thread_names) |
1596 | - scene->emit_change_event(); |
1597 | - |
1598 | - compositor.stop(); |
1599 | - |
1600 | - auto const& thread_names = db_compositor_factory->thread_names; |
1601 | - |
1602 | - for (size_t i = 0; i < thread_names.size(); ++i) |
1603 | - EXPECT_THAT(thread_names[i], Eq("Mir/Comp")) << "i=" << i; |
1604 | + EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(nbuffers)); |
1605 | } |
PASSED: Continuous integration, rev:1879 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- ci/2563/ jenkins. qa.ubuntu. com/job/ mir-android- utopic- i386-build/ 1544 jenkins. qa.ubuntu. com/job/ mir-clang- utopic- amd64-build/ 1550 jenkins. qa.ubuntu. com/job/ mir-mediumtests -utopic- touch/1526 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- amd64-ci/ 1085 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- amd64-ci/ 1085/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- utopic- armhf/463 jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- utopic- armhf/463/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/2603 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 12409
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: s-jenkins. ubuntu- ci:8080/ job/mir- team-mir- development- branch- ci/2563/ rebuild
http://