Mir

Merge lp:~albaguirre/mir/recycle-compositor-threads into lp:mir

Proposed by Alberto Aguirre
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
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)

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
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.

[1] http://en.wikipedia.org/wiki/Thread_pool_pattern

review: Needs Information
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

OK, so two questions right now:

(1) Does the thread pool really need to be compositor-specific? That does 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 :)

Revision history for this message
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_ptr<std::thread> run(void const* id, std::function<void()> const& functor) = 0;
virtual void clear_unused_threads() = 0;
};

// And either dependency on abstract ThreadPool:
MultiThreadedCompositor(...std::shared_ptr<ThreadPool> const& tp...);
// 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 MultiThreadedCompositor { ReusableThreadPool thread_pool; };

So MultiThreadedCompositor stays as it is more or less, and reverting to the 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"

review: Needs Information
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

> OK, so two questions right now:
>
> (1) Does the thread pool really need to be compositor-specific? That does
> 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).

Revision history for this message
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_ptr<std::thread> run(void const* id, std::function<void()>
> const& functor) = 0;
> virtual void clear_unused_threads() = 0;
> };
>
> // And either dependency on abstract ThreadPool:
> MultiThreadedCompositor(...std::shared_ptr<ThreadPool> const& tp...);
> // 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 MultiThreadedCompositor { ReusableThreadPool thread_pool; };
>
> So MultiThreadedCompositor stays as it is more or less, and reverting to the
> 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.

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }

Subscribers

People subscribed via source and target branches