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
=== modified file 'include/test/mir_test/signal.h'
--- include/test/mir_test/signal.h 2014-05-22 20:48:20 +0000
+++ include/test/mir_test/signal.h 2014-08-30 16:57:50 +0000
@@ -37,6 +37,7 @@
3737
38 void raise();38 void raise();
39 bool raised();39 bool raised();
40 void reset();
4041
41 void wait();42 void wait();
42 template<typename rep, typename period>43 template<typename rep, typename period>
4344
=== added file 'include/test/mir_test_doubles/mock_compositor_loop.h'
--- include/test/mir_test_doubles/mock_compositor_loop.h 1970-01-01 00:00:00 +0000
+++ include/test/mir_test_doubles/mock_compositor_loop.h 2014-08-30 16:57:50 +0000
@@ -0,0 +1,44 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 */
18
19#ifndef MIR_TEST_DOUBLES_MOCK_COMPOSITOR_LOOP_H_
20#define MIR_TEST_DOUBLES_MOCK_COMPOSITOR_LOOP_H_
21
22#include "src/server/compositor/compositor_thread.h"
23#include <gmock/gmock.h>
24
25namespace mir
26{
27namespace test
28{
29namespace doubles
30{
31
32class MockCompositorLoop : public compositor::CompositorLoop
33{
34public:
35 MOCK_METHOD0(run, void());
36 MOCK_METHOD0(stop, void());
37 MOCK_METHOD1(schedule_compositing, void(int));
38};
39
40}
41}
42}
43
44#endif
045
=== added file 'include/test/mir_test_doubles/mock_compositor_thread.h'
--- include/test/mir_test_doubles/mock_compositor_thread.h 1970-01-01 00:00:00 +0000
+++ include/test/mir_test_doubles/mock_compositor_thread.h 2014-08-30 16:57:50 +0000
@@ -0,0 +1,66 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 */
18
19#ifndef MIR_TEST_DOUBLES_MOCK_COMPOSITOR_THREAD_H_
20#define MIR_TEST_DOUBLES_MOCK_COMPOSITOR_THREAD_H_
21
22#include "src/server/compositor/compositor_thread.h"
23#include <gmock/gmock.h>
24
25namespace mir
26{
27namespace test
28{
29namespace doubles
30{
31
32class MockCompositorThread : public compositor::CompositorThread
33{
34public:
35 MockCompositorThread(std::unique_ptr<compositor::CompositorLoop>& loop)
36 : compositor::CompositorThread(std::move(loop))
37 {
38 }
39
40 ~MockCompositorThread()
41 {
42 destroyed();
43 }
44
45 void run(std::unique_ptr<compositor::CompositorLoop> loop) override
46 {
47 run_();
48 compositor::CompositorThread::run(std::move(loop));
49 }
50
51 void pause() override
52 {
53 pause_();
54 compositor::CompositorThread::pause();
55 }
56
57 MOCK_METHOD0(run_, void());
58 MOCK_METHOD0(pause_, void());
59 MOCK_METHOD0(destroyed, void());
60};
61
62}
63}
64}
65
66#endif
067
=== modified file 'src/server/compositor/CMakeLists.txt'
--- src/server/compositor/CMakeLists.txt 2014-08-08 02:41:51 +0000
+++ src/server/compositor/CMakeLists.txt 2014-08-30 16:57:50 +0000
@@ -16,6 +16,8 @@
16 timeout_frame_dropping_policy_factory.cpp16 timeout_frame_dropping_policy_factory.cpp
17 buffer_queue.cpp17 buffer_queue.cpp
18 recently_used_cache.cpp18 recently_used_cache.cpp
19 compositor_thread.cpp
20 compositor_thread_factory.cpp
19)21)
2022
21ADD_LIBRARY(23ADD_LIBRARY(
2224
=== added file 'src/server/compositor/compositor_thread.cpp'
--- src/server/compositor/compositor_thread.cpp 1970-01-01 00:00:00 +0000
+++ src/server/compositor/compositor_thread.cpp 2014-08-30 16:57:50 +0000
@@ -0,0 +1,141 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 */
18
19#include "compositor_thread.h"
20#include "mir/run_mir.h"
21#include "mir/thread_name.h"
22
23#include <boost/throw_exception.hpp>
24
25namespace mc = mir::compositor;
26
27mc::CompositorThread::CompositorThread(std::unique_ptr<mc::CompositorLoop> loop)
28 : compositor_loop{std::move(loop)},
29 state{CompositorThreadState::running},
30 thread{&mc::CompositorThread::thread_entry, this}
31{
32}
33
34mc::CompositorThread::~CompositorThread()
35{
36 stop();
37 if (thread.joinable())
38 thread.join();
39}
40
41void mc::CompositorThread::thread_entry() noexcept // noexcept is important! (LP: #1237332)
42try
43{
44 mir::set_thread_name("Mir/Comp");
45
46 std::unique_lock<std::mutex> lock{run_mutex};
47 while (state != CompositorThreadState::stopping)
48 {
49 run_cv.wait(lock, [&]{ return state != CompositorThreadState::paused; });
50
51 if (state == CompositorThreadState::running)
52 {
53 lock.unlock();
54 compositor_loop->run();
55 lock.lock();
56 }
57
58 if (state == CompositorThreadState::pausing)
59 {
60 state = CompositorThreadState::paused;
61 paused_cv.notify_all();
62 }
63 }
64}
65catch(...)
66{
67 std::lock_guard<std::mutex> lock{run_mutex};
68 state = CompositorThreadState::stopping;
69 compositor_loop = nullptr;
70 paused_cv.notify_all();
71
72 mir::terminate_with_current_exception();
73}
74
75void mc::CompositorThread::run(std::unique_ptr<CompositorLoop> loop)
76{
77 std::lock_guard<std::mutex> lock{run_mutex};
78
79 if (state == CompositorThreadState::running)
80 {
81 BOOST_THROW_EXCEPTION(std::logic_error("Another compositor loop is already running!"));
82 }
83
84 compositor_loop = std::move(loop);
85
86 state = CompositorThreadState::running;
87 run_cv.notify_one();
88}
89
90void mc::CompositorThread::pause()
91{
92 std::unique_lock<std::mutex> lock{run_mutex};
93 pause(lock);
94}
95
96void mc::CompositorThread::stop()
97{
98 std::lock_guard<std::mutex> lock{run_mutex};
99
100 state = CompositorThreadState::stopping;
101
102 if (compositor_loop)
103 compositor_loop->stop();
104
105 run_cv.notify_one();
106}
107
108void mc::CompositorThread::schedule_compositing(int num_frames)
109{
110 std::lock_guard<std::mutex> lock{run_mutex};
111
112 if (state == CompositorThreadState::running)
113 {
114 compositor_loop->schedule_compositing(num_frames);
115 }
116}
117
118bool mc::CompositorThread::is_running() const
119{
120 std::lock_guard<std::mutex> lock{run_mutex};
121 return state == CompositorThreadState::running;
122}
123
124void mc::CompositorThread::pause(std::unique_lock<std::mutex>& lock)
125{
126 if (state != CompositorThreadState::running)
127 {
128 return;
129 }
130
131 state = mc::CompositorThreadState::pausing;
132 compositor_loop->stop();
133 run_cv.notify_one();
134
135 paused_cv.wait(lock, [&]{
136 return state == CompositorThreadState::paused ||
137 state == CompositorThreadState::stopping;
138 });
139
140 compositor_loop = nullptr;
141}
0142
=== added file 'src/server/compositor/compositor_thread.h'
--- src/server/compositor/compositor_thread.h 1970-01-01 00:00:00 +0000
+++ src/server/compositor/compositor_thread.h 2014-08-30 16:57:50 +0000
@@ -0,0 +1,89 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 */
18
19#ifndef MIR_COMPOSITOR_THREAD_H_
20#define MIR_COMPOSITOR_THREAD_H_
21
22#include "mir/compositor/compositor.h"
23
24#include <mutex>
25#include <memory>
26#include <thread>
27#include <condition_variable>
28
29namespace mir
30{
31namespace compositor
32{
33
34class CompositorLoop
35{
36public:
37 CompositorLoop() = default;
38 virtual ~CompositorLoop() = default;
39
40 virtual void run() = 0;
41 virtual void stop() = 0;
42 virtual void schedule_compositing(int num_frames) = 0;
43
44private:
45 CompositorLoop(CompositorLoop const&) = delete;
46 CompositorLoop& operator=(CompositorLoop const&) = delete;
47};
48
49enum class CompositorThreadState
50{
51 stopping,
52 pausing,
53 paused,
54 running
55};
56
57class CompositorThread
58{
59public:
60 CompositorThread(std::unique_ptr<CompositorLoop> loop);
61 virtual ~CompositorThread();
62
63 virtual void run(std::unique_ptr<CompositorLoop> loop);
64 virtual void pause();
65 virtual void schedule_compositing(int num_frames);
66
67 virtual bool is_running() const;
68
69private:
70 void stop();
71 void thread_entry() noexcept;
72 void pause(std::unique_lock<std::mutex>& lock);
73
74 CompositorThread(CompositorThread&) = delete;
75 CompositorThread(CompositorThread const&) = delete;
76 CompositorThread& operator=(CompositorThread const&) = delete;
77
78 std::unique_ptr<CompositorLoop> compositor_loop;
79
80 CompositorThreadState state;
81 std::mutex mutable run_mutex ;
82 std::condition_variable run_cv;
83 std::condition_variable paused_cv;
84 std::thread thread;
85};
86
87}
88}
89#endif
090
=== added file 'src/server/compositor/compositor_thread_factory.cpp'
--- src/server/compositor/compositor_thread_factory.cpp 1970-01-01 00:00:00 +0000
+++ src/server/compositor/compositor_thread_factory.cpp 2014-08-30 16:57:50 +0000
@@ -0,0 +1,32 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 */
18
19#include "compositor_thread_factory.h"
20#include "compositor_thread.h"
21
22namespace mc = mir::compositor;
23
24mc::CompositorThreadFactory::CompositorThreadFactory() = default;
25mc::CompositorThreadFactory::~CompositorThreadFactory() = default;
26
27std::unique_ptr<mc::CompositorThread>
28mc::CompositorThreadFactory::create_compositor_thread_for(std::unique_ptr<mc::CompositorLoop> loop)
29{
30 std::unique_ptr<CompositorThread> compositor_thread{new CompositorThread(std::move(loop))};
31 return compositor_thread;
32}
033
=== added file 'src/server/compositor/compositor_thread_factory.h'
--- src/server/compositor/compositor_thread_factory.h 1970-01-01 00:00:00 +0000
+++ src/server/compositor/compositor_thread_factory.h 2014-08-30 16:57:50 +0000
@@ -0,0 +1,44 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 */
18
19#ifndef MIR_COMPOSITOR_COMPOSITOR_THREAD_FACTORY_H_
20#define MIR_COMPOSITOR_COMPOSITOR_THREAD_FACTORY_H_
21
22#include <memory>
23
24namespace mir
25{
26namespace compositor
27{
28class CompositorThread;
29class CompositorLoop;
30class CompositorThreadFactory
31{
32public:
33 CompositorThreadFactory();
34 virtual ~CompositorThreadFactory();
35
36 virtual std::unique_ptr<CompositorThread> create_compositor_thread_for(
37 std::unique_ptr<CompositorLoop> loop);
38};
39
40}
41}
42
43
44#endif
045
=== modified file 'src/server/compositor/default_configuration.cpp'
--- src/server/compositor/default_configuration.cpp 2014-08-06 03:10:56 +0000
+++ src/server/compositor/default_configuration.cpp 2014-08-30 16:57:50 +0000
@@ -20,6 +20,7 @@
2020
21#include "buffer_stream_factory.h"21#include "buffer_stream_factory.h"
22#include "default_display_buffer_compositor_factory.h"22#include "default_display_buffer_compositor_factory.h"
23#include "compositor_thread_factory.h"
23#include "multi_threaded_compositor.h"24#include "multi_threaded_compositor.h"
24#include "gl_renderer_factory.h"25#include "gl_renderer_factory.h"
25#include "compositing_screencast.h"26#include "compositing_screencast.h"
@@ -78,6 +79,7 @@
78 the_display(),79 the_display(),
79 the_scene(),80 the_scene(),
80 the_display_buffer_compositor_factory(),81 the_display_buffer_compositor_factory(),
82 std::make_shared<mc::CompositorThreadFactory>(),
81 the_compositor_report(),83 the_compositor_report(),
82 !the_options()->is_set(options::host_socket_opt));84 !the_options()->is_set(options::host_socket_opt));
83 });85 });
8486
=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
--- src/server/compositor/multi_threaded_compositor.cpp 2014-08-06 03:10:56 +0000
+++ src/server/compositor/multi_threaded_compositor.cpp 2014-08-30 16:57:50 +0000
@@ -13,10 +13,14 @@
13 * You should have received a copy of the GNU General Public License13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 * Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */18 */
1819
19#include "multi_threaded_compositor.h"20#include "multi_threaded_compositor.h"
21#include "compositor_thread.h"
22#include "compositor_thread_factory.h"
23
20#include "mir/graphics/display.h"24#include "mir/graphics/display.h"
21#include "mir/graphics/display_buffer.h"25#include "mir/graphics/display_buffer.h"
22#include "mir/compositor/display_buffer_compositor.h"26#include "mir/compositor/display_buffer_compositor.h"
@@ -85,12 +89,13 @@
85 mg::DisplayBuffer& buffer;89 mg::DisplayBuffer& buffer;
86};90};
8791
88class CompositingFunctor92class DisplayBufferCompositingLoop : public CompositorLoop
89{93{
90public:94public:
91 CompositingFunctor(std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory,95 DisplayBufferCompositingLoop(
92 mg::DisplayBuffer& buffer,96 std::shared_ptr<mc::DisplayBufferCompositorFactory> const& db_compositor_factory,
93 std::shared_ptr<CompositorReport> const& report)97 mg::DisplayBuffer& buffer,
98 std::shared_ptr<CompositorReport> const& report)
94 : display_buffer_compositor_factory{db_compositor_factory},99 : display_buffer_compositor_factory{db_compositor_factory},
95 buffer(buffer),100 buffer(buffer),
96 running{true},101 running{true},
@@ -99,11 +104,8 @@
99 {104 {
100 }105 }
101106
102 void operator()() noexcept // noexcept is important! (LP: #1237332)107 void run()
103 try
104 {108 {
105 mir::set_thread_name("Mir/Comp");
106
107 /*109 /*
108 * Make the buffer the current rendering target, and release110 * Make the buffer the current rendering target, and release
109 * it when the thread is finished.111 * it when the thread is finished.
@@ -148,10 +150,6 @@
148 }150 }
149 }151 }
150 }152 }
151 catch(...)
152 {
153 mir::terminate_with_current_exception();
154 }
155153
156 void schedule_compositing(int num_frames)154 void schedule_compositing(int num_frames)
157 {155 {
@@ -188,11 +186,13 @@
188 std::shared_ptr<mg::Display> const& display,186 std::shared_ptr<mg::Display> const& display,
189 std::shared_ptr<mc::Scene> const& scene,187 std::shared_ptr<mc::Scene> const& scene,
190 std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory,188 std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory,
189 std::shared_ptr<CompositorThreadFactory> const& compositor_thread_factory,
191 std::shared_ptr<CompositorReport> const& compositor_report,190 std::shared_ptr<CompositorReport> const& compositor_report,
192 bool compose_on_start)191 bool compose_on_start)
193 : display{display},192 : display{display},
194 scene{scene},193 scene{scene},
195 display_buffer_compositor_factory{db_compositor_factory},194 display_buffer_compositor_factory{db_compositor_factory},
195 compositor_thread_factory{compositor_thread_factory},
196 report{compositor_report},196 report{compositor_report},
197 state{CompositorState::stopped},197 state{CompositorState::stopped},
198 compose_on_start{compose_on_start}198 compose_on_start{compose_on_start}
@@ -218,8 +218,8 @@
218 std::unique_lock<std::mutex> lk(state_guard);218 std::unique_lock<std::mutex> lk(state_guard);
219219
220 report->scheduled();220 report->scheduled();
221 for (auto& f : thread_functors)221 for (auto& t : threads)
222 f->schedule_compositing(num);222 t.second->schedule_compositing(num);
223}223}
224224
225void mc::MultiThreadedCompositor::start()225void mc::MultiThreadedCompositor::start()
@@ -236,14 +236,15 @@
236 /* To cleanup state if any code below throws */236 /* To cleanup state if any code below throws */
237 ApplyIfUnwinding cleanup_if_unwinding{237 ApplyIfUnwinding cleanup_if_unwinding{
238 [this, &lk]{238 [this, &lk]{
239 destroy_compositing_threads(lk);239 stop_compositing_threads(lk);
240 threads.clear();
240 }};241 }};
241242
242 lk.unlock();243 lk.unlock();
243 scene->add_observer(observer);244 scene->add_observer(observer);
244 lk.lock();245 lk.lock();
245246
246 create_compositing_threads();247 start_compositing_threads();
247248
248 /* Optional first render */249 /* Optional first render */
249 if (compose_on_start)250 if (compose_on_start)
@@ -274,29 +275,49 @@
274 scene->remove_observer(observer);275 scene->remove_observer(observer);
275 lk.lock();276 lk.lock();
276277
277 destroy_compositing_threads(lk);278 stop_compositing_threads(lk);
278279
279 // If the compositor is restarted we've likely got clients blocked280 // If the compositor is restarted we've likely got clients blocked
280 // so we will need to schedule compositing immediately281 // so we will need to schedule compositing immediately
281 compose_on_start = true;282 compose_on_start = true;
282}283}
283284
284void mc::MultiThreadedCompositor::create_compositing_threads()285void mc::MultiThreadedCompositor::start_compositing_threads()
285{286{
286 /* Start the display buffer compositing threads */287 /* Start the display buffer compositing threads */
287 display->for_each_display_buffer([this](mg::DisplayBuffer& buffer)288 display->for_each_display_buffer([this](mg::DisplayBuffer& buffer)
288 {289 {
289 auto thread_functor_raw = new mc::CompositingFunctor{display_buffer_compositor_factory, buffer, report};290
290 auto thread_functor = std::unique_ptr<mc::CompositingFunctor>(thread_functor_raw);291 auto loop_raw = new mc::DisplayBufferCompositingLoop{display_buffer_compositor_factory, buffer, report};
291292 auto loop = std::unique_ptr<mc::DisplayBufferCompositingLoop>(loop_raw);
292 threads.push_back(std::thread{std::ref(*thread_functor)});293
293 thread_functors.push_back(std::move(thread_functor));294 auto it = threads.find(&buffer);
295 if (it != threads.end())
296 {
297 auto& compositor_thread = it->second;
298 compositor_thread->run(std::move(loop));
299 }
300 else
301 {
302 auto compositor_thread = compositor_thread_factory->create_compositor_thread_for(std::move(loop));
303 threads[&buffer] = std::move(compositor_thread);
304 }
294 });305 });
295306
307 // Some display buffers may not be active anymore - clean up their respective threads.
308 // Note: std::remove_if does not apply to associative containers.
309 for (auto it = threads.begin(), end = threads.end(); it != end;)
310 {
311 if (!it->second->is_running())
312 it = threads.erase(it);
313 else
314 ++it;
315 }
316
296 state = CompositorState::started;317 state = CompositorState::started;
297}318}
298319
299void mc::MultiThreadedCompositor::destroy_compositing_threads(std::unique_lock<std::mutex>& lock)320void mc::MultiThreadedCompositor::stop_compositing_threads(std::unique_lock<std::mutex>& lock)
300{321{
301 /* Could be called during unwinding,322 /* Could be called during unwinding,
302 * ensure the lock is held before changing state323 * ensure the lock is held before changing state
@@ -304,14 +325,8 @@
304 if(!lock.owns_lock())325 if(!lock.owns_lock())
305 lock.lock();326 lock.lock();
306327
307 for (auto& f : thread_functors)
308 f->stop();
309
310 for (auto& t : threads)328 for (auto& t : threads)
311 t.join();329 t.second->pause();
312
313 thread_functors.clear();
314 threads.clear();
315330
316 report->stopped();331 report->stopped();
317332
318333
=== modified file 'src/server/compositor/multi_threaded_compositor.h'
--- src/server/compositor/multi_threaded_compositor.h 2014-08-06 03:10:56 +0000
+++ src/server/compositor/multi_threaded_compositor.h 2014-08-30 16:57:50 +0000
@@ -23,7 +23,7 @@
2323
24#include <mutex>24#include <mutex>
25#include <memory>25#include <memory>
26#include <vector>26#include <map>
27#include <thread>27#include <thread>
2828
29namespace mir29namespace mir
@@ -31,6 +31,7 @@
31namespace graphics31namespace graphics
32{32{
33class Display;33class Display;
34class DisplayBuffer;
34}35}
35namespace scene36namespace scene
36{37{
@@ -41,8 +42,9 @@
41{42{
4243
43class DisplayBufferCompositorFactory;44class DisplayBufferCompositorFactory;
44class CompositingFunctor;45class CompositorThreadFactory;
45class Scene;46class Scene;
47class CompositorThread;
46class CompositorReport;48class CompositorReport;
4749
48enum class CompositorState50enum class CompositorState
@@ -59,6 +61,7 @@
59 MultiThreadedCompositor(std::shared_ptr<graphics::Display> const& display,61 MultiThreadedCompositor(std::shared_ptr<graphics::Display> const& display,
60 std::shared_ptr<Scene> const& scene,62 std::shared_ptr<Scene> const& scene,
61 std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory,63 std::shared_ptr<DisplayBufferCompositorFactory> const& db_compositor_factory,
64 std::shared_ptr<CompositorThreadFactory> const& compositor_thread_factory,
62 std::shared_ptr<CompositorReport> const& compositor_report,65 std::shared_ptr<CompositorReport> const& compositor_report,
63 bool compose_on_start);66 bool compose_on_start);
64 ~MultiThreadedCompositor();67 ~MultiThreadedCompositor();
@@ -67,23 +70,22 @@
67 void stop();70 void stop();
6871
69private:72private:
70 void create_compositing_threads();73 void start_compositing_threads();
71 void destroy_compositing_threads(std::unique_lock<std::mutex>& lock);74 void stop_compositing_threads(std::unique_lock<std::mutex>& lock);
7275
73 std::shared_ptr<graphics::Display> const display;76 std::shared_ptr<graphics::Display> const display;
74 std::shared_ptr<Scene> const scene;77 std::shared_ptr<Scene> const scene;
75 std::shared_ptr<DisplayBufferCompositorFactory> const display_buffer_compositor_factory;78 std::shared_ptr<DisplayBufferCompositorFactory> const display_buffer_compositor_factory;
79 std::shared_ptr<CompositorThreadFactory> const compositor_thread_factory;
76 std::shared_ptr<CompositorReport> const report;80 std::shared_ptr<CompositorReport> const report;
7781
78 std::vector<std::unique_ptr<CompositingFunctor>> thread_functors;82 std::map<graphics::DisplayBuffer*, std::unique_ptr<CompositorThread>> threads;
79 std::vector<std::thread> threads;
80
81 std::mutex state_guard;83 std::mutex state_guard;
82 CompositorState state;84 CompositorState state;
83 bool compose_on_start;85 bool compose_on_start;
8486
85 void schedule_compositing(int number_composites);87 void schedule_compositing(int number_composites);
86 88
87 std::shared_ptr<mir::scene::Observer> observer;89 std::shared_ptr<mir::scene::Observer> observer;
88};90};
8991
9092
=== modified file 'tests/integration-tests/test_surface_stack_with_compositor.cpp'
--- tests/integration-tests/test_surface_stack_with_compositor.cpp 2014-08-06 03:10:56 +0000
+++ tests/integration-tests/test_surface_stack_with_compositor.cpp 2014-08-30 16:57:50 +0000
@@ -22,6 +22,7 @@
22#include "src/server/compositor/gl_renderer_factory.h"22#include "src/server/compositor/gl_renderer_factory.h"
23#include "src/server/scene/basic_surface.h"23#include "src/server/scene/basic_surface.h"
24#include "src/server/compositor/default_display_buffer_compositor_factory.h"24#include "src/server/compositor/default_display_buffer_compositor_factory.h"
25#include "src/server/compositor/compositor_thread_factory.h"
25#include "src/server/compositor/multi_threaded_compositor.h"26#include "src/server/compositor/multi_threaded_compositor.h"
26#include "mir_test/fake_shared.h"27#include "mir_test/fake_shared.h"
27#include "mir_test_doubles/mock_buffer_stream.h"28#include "mir_test_doubles/mock_buffer_stream.h"
@@ -158,6 +159,7 @@
158 mt::fake_shared(stack),159 mt::fake_shared(stack),
159 mt::fake_shared(renderer_factory),160 mt::fake_shared(renderer_factory),
160 null_comp_report};161 null_comp_report};
162 mc::CompositorThreadFactory comp_thread_factory;
161};163};
162}164}
163165
@@ -167,6 +169,7 @@
167 mt::fake_shared(stub_display),169 mt::fake_shared(stub_display),
168 mt::fake_shared(stack),170 mt::fake_shared(stack),
169 mt::fake_shared(dbc_factory),171 mt::fake_shared(dbc_factory),
172 mt::fake_shared(comp_thread_factory),
170 null_comp_report, true);173 null_comp_report, true);
171 mt_compositor.start();174 mt_compositor.start();
172175
@@ -180,6 +183,7 @@
180 mt::fake_shared(stub_display),183 mt::fake_shared(stub_display),
181 mt::fake_shared(stack),184 mt::fake_shared(stack),
182 mt::fake_shared(dbc_factory),185 mt::fake_shared(dbc_factory),
186 mt::fake_shared(comp_thread_factory),
183 null_comp_report, false);187 null_comp_report, false);
184 mt_compositor.start();188 mt_compositor.start();
185189
@@ -193,6 +197,7 @@
193 mt::fake_shared(stub_display),197 mt::fake_shared(stub_display),
194 mt::fake_shared(stack),198 mt::fake_shared(stack),
195 mt::fake_shared(dbc_factory),199 mt::fake_shared(dbc_factory),
200 mt::fake_shared(comp_thread_factory),
196 null_comp_report, false);201 null_comp_report, false);
197 mt_compositor.start();202 mt_compositor.start();
198203
@@ -214,6 +219,7 @@
214 mt::fake_shared(stub_display),219 mt::fake_shared(stub_display),
215 mt::fake_shared(stack),220 mt::fake_shared(stack),
216 mt::fake_shared(dbc_factory),221 mt::fake_shared(dbc_factory),
222 mt::fake_shared(comp_thread_factory),
217 null_comp_report, false);223 null_comp_report, false);
218 mt_compositor.start();224 mt_compositor.start();
219225
@@ -236,6 +242,7 @@
236 mt::fake_shared(stub_display),242 mt::fake_shared(stub_display),
237 mt::fake_shared(stack),243 mt::fake_shared(stack),
238 mt::fake_shared(dbc_factory),244 mt::fake_shared(dbc_factory),
245 mt::fake_shared(comp_thread_factory),
239 null_comp_report, false);246 null_comp_report, false);
240 mt_compositor.start();247 mt_compositor.start();
241248
@@ -256,6 +263,7 @@
256 mt::fake_shared(stub_display),263 mt::fake_shared(stub_display),
257 mt::fake_shared(stack),264 mt::fake_shared(stack),
258 mt::fake_shared(dbc_factory),265 mt::fake_shared(dbc_factory),
266 mt::fake_shared(comp_thread_factory),
259 null_comp_report, false);267 null_comp_report, false);
260 mt_compositor.start();268 mt_compositor.start();
261269
@@ -280,6 +288,7 @@
280 mt::fake_shared(stub_display),288 mt::fake_shared(stub_display),
281 mt::fake_shared(stack),289 mt::fake_shared(stack),
282 mt::fake_shared(dbc_factory),290 mt::fake_shared(dbc_factory),
291 mt::fake_shared(comp_thread_factory),
283 null_comp_report, false);292 null_comp_report, false);
284293
285 mt_compositor.start();294 mt_compositor.start();
@@ -298,6 +307,7 @@
298 mt::fake_shared(stub_display),307 mt::fake_shared(stub_display),
299 mt::fake_shared(stack),308 mt::fake_shared(stack),
300 mt::fake_shared(dbc_factory),309 mt::fake_shared(dbc_factory),
310 mt::fake_shared(comp_thread_factory),
301 null_comp_report, false);311 null_comp_report, false);
302312
303 mt_compositor.start();313 mt_compositor.start();
@@ -318,6 +328,7 @@
318 mt::fake_shared(stub_display),328 mt::fake_shared(stub_display),
319 mt::fake_shared(stack),329 mt::fake_shared(stack),
320 mt::fake_shared(dbc_factory),330 mt::fake_shared(dbc_factory),
331 mt::fake_shared(comp_thread_factory),
321 null_comp_report, false);332 null_comp_report, false);
322333
323 mt_compositor.start();334 mt_compositor.start();
324335
=== modified file 'tests/mir_test/signal.cpp'
--- tests/mir_test/signal.cpp 2014-05-22 20:48:20 +0000
+++ tests/mir_test/signal.cpp 2014-08-30 16:57:50 +0000
@@ -44,3 +44,9 @@
44 std::unique_lock<decltype(mutex)> lock(mutex);44 std::unique_lock<decltype(mutex)> lock(mutex);
45 cv.wait(lock, [this]() { return signalled; });45 cv.wait(lock, [this]() { return signalled; });
46}46}
47
48void mt::Signal::reset()
49{
50 std::unique_lock<decltype(mutex)> lock(mutex);
51 signalled = false;
52}
4753
=== modified file 'tests/unit-tests/compositor/CMakeLists.txt'
--- tests/unit-tests/compositor/CMakeLists.txt 2014-08-06 03:10:56 +0000
+++ tests/unit-tests/compositor/CMakeLists.txt 2014-08-30 16:57:50 +0000
@@ -10,6 +10,7 @@
10 ${CMAKE_CURRENT_SOURCE_DIR}/test_screencast_display_buffer.cpp10 ${CMAKE_CURRENT_SOURCE_DIR}/test_screencast_display_buffer.cpp
11 ${CMAKE_CURRENT_SOURCE_DIR}/test_compositing_screencast.cpp11 ${CMAKE_CURRENT_SOURCE_DIR}/test_compositing_screencast.cpp
12 ${CMAKE_CURRENT_SOURCE_DIR}/test_timeout_frame_dropping_policy.cpp12 ${CMAKE_CURRENT_SOURCE_DIR}/test_timeout_frame_dropping_policy.cpp
13 ${CMAKE_CURRENT_SOURCE_DIR}/test_compositor_thread.cpp
13)14)
1415
15set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES} PARENT_SCOPE)16set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES} PARENT_SCOPE)
1617
=== added file 'tests/unit-tests/compositor/test_compositor_thread.cpp'
--- tests/unit-tests/compositor/test_compositor_thread.cpp 1970-01-01 00:00:00 +0000
+++ tests/unit-tests/compositor/test_compositor_thread.cpp 2014-08-30 16:57:50 +0000
@@ -0,0 +1,256 @@
1/*
2 * Copyright © 2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alberto Aguirre <alberto.aguirre@canonical.com>
17 */
18
19#include "src/server/compositor/compositor_thread.h"
20
21#include "mir_test_doubles/mock_compositor_loop.h"
22#include "mir_test/current_thread_name.h"
23#include "mir_test/signal.h"
24#include "mir/raii.h"
25
26#include <memory>
27#include <chrono>
28#include <thread>
29#include <exception>
30#include <csignal>
31
32#include <gmock/gmock.h>
33#include <gtest/gtest.h>
34
35namespace mc = mir::compositor;
36
37namespace mt = mir::test;
38namespace mtd = mir::test::doubles;
39
40namespace
41{
42
43extern "C" { typedef void (*sig_handler)(int); }
44volatile sig_handler old_signal_handler = nullptr;
45mt::Signal sigterm_signal;
46
47extern "C" void signal_handler(int sig)
48{
49 if (sig == SIGTERM) {
50 sigterm_signal.raise();
51 }
52
53 signal(sig, old_signal_handler);
54}
55
56extern "C" void ignore_signal(int sig)
57{
58 signal(sig, old_signal_handler);
59}
60
61class StubCompositorLoop : public mc::CompositorLoop
62{
63public:
64 void run() override {}
65 void stop() override {}
66 void schedule_compositing(int) override {}
67};
68
69class CompositorThread : public testing::Test
70{
71public:
72 CompositorThread()
73 {
74 new_mock_loop();
75 }
76
77 void new_mock_loop()
78 {
79 std::unique_ptr<mtd::MockCompositorLoop> loop{new testing::NiceMock<mtd::MockCompositorLoop>()};
80 mock_loop = std::move(loop);
81 ON_CALL(*mock_loop, run())
82 .WillByDefault(InvokeWithoutArgs(this, &CompositorThread::raise_run_signal));
83 run_signal.reset();
84 }
85
86 void raise_run_signal()
87 {
88 run_signal.raise();
89 std::this_thread::yield();
90 }
91
92 std::unique_ptr<mtd::MockCompositorLoop> mock_loop;
93 mt::Signal run_signal;
94};
95}
96
97TEST_F(CompositorThread, runs_immediately_on_construction)
98{
99 using namespace testing;
100
101 EXPECT_CALL(*mock_loop, run())
102 .Times(AnyNumber());
103
104 mc::CompositorThread thread{std::move(mock_loop)};
105
106 run_signal.wait_for(std::chrono::seconds{1});
107 EXPECT_TRUE(run_signal.raised());
108}
109
110TEST_F(CompositorThread, can_pause)
111{
112 using namespace testing;
113
114 EXPECT_CALL(*mock_loop, stop());
115
116 mc::CompositorThread thread{std::move(mock_loop)};
117
118 thread.pause();
119
120 EXPECT_FALSE(thread.is_running());
121}
122
123TEST_F(CompositorThread, only_pauses_when_thread_is_already_running)
124{
125 using namespace testing;
126
127 EXPECT_CALL(*mock_loop, stop())
128 .Times(1);
129
130 mc::CompositorThread thread{std::move(mock_loop)};
131
132 thread.pause();
133
134 EXPECT_FALSE(thread.is_running());
135
136 thread.pause();
137}
138
139TEST_F(CompositorThread, destructor_stops_loop)
140{
141 using namespace testing;
142
143 EXPECT_CALL(*mock_loop, stop());
144 mc::CompositorThread thread{std::move(mock_loop)};
145}
146
147TEST_F(CompositorThread, raises_sigterm_on_thread_exception)
148{
149 using namespace testing;
150
151 sigterm_signal.reset();
152
153 auto const raii = mir::raii::paired_calls(
154 [&]{ old_signal_handler = signal(SIGTERM, signal_handler); },
155 [&]{ signal(SIGTERM, old_signal_handler); });
156
157 EXPECT_CALL(*mock_loop, run())
158 .WillOnce(Throw(std::logic_error("")));
159
160 mc::CompositorThread thread{std::move(mock_loop)};
161
162 sigterm_signal.wait_for(std::chrono::seconds{1});
163 EXPECT_TRUE(sigterm_signal.raised());
164}
165
166TEST_F(CompositorThread, pause_does_not_block_after_thread_exception)
167{
168 using namespace testing;
169
170 auto const raii = mir::raii::paired_calls(
171 [&]{ old_signal_handler = signal(SIGTERM, ignore_signal); },
172 [&]{ signal(SIGTERM, old_signal_handler); });
173
174 auto throw_on_run = [this] {
175 run_signal.raise();
176 throw std::runtime_error("");
177 };
178 EXPECT_CALL(*mock_loop, run())
179 .WillOnce(Invoke(throw_on_run));
180
181 mc::CompositorThread thread{std::move(mock_loop)};
182
183 run_signal.wait_for(std::chrono::seconds{1});
184 EXPECT_TRUE(run_signal.raised());
185
186 thread.pause();
187}
188
189TEST_F(CompositorThread, names_itself)
190{
191 using namespace testing;
192
193 std::string thread_name;
194 auto check_thread_name = [&]{
195 thread_name = mt::current_thread_name();
196 run_signal.raise();
197 };
198
199 EXPECT_CALL(*mock_loop, run())
200 .WillRepeatedly(InvokeWithoutArgs(check_thread_name));
201
202 mc::CompositorThread thread{std::move(mock_loop)};
203
204 run_signal.wait_for(std::chrono::seconds{1});
205
206 EXPECT_TRUE(run_signal.raised());
207 EXPECT_THAT(thread_name, Eq("Mir/Comp"));
208}
209
210TEST_F(CompositorThread, makes_loop_schedule_compositing)
211{
212 using namespace testing;
213
214 EXPECT_CALL(*mock_loop, schedule_compositing(_));
215
216 mc::CompositorThread thread{std::move(mock_loop)};
217 int const arbitrary_num_frames = 1;
218 thread.schedule_compositing(arbitrary_num_frames);
219}
220
221TEST_F(CompositorThread, throws_when_run_invoked_before_pause)
222{
223 using namespace testing;
224
225 mc::CompositorThread thread{std::move(mock_loop)};
226
227 new_mock_loop();
228
229 EXPECT_THROW(thread.run(std::move(mock_loop)), std::logic_error);
230}
231
232TEST_F(CompositorThread, can_run_another_loop)
233{
234 using namespace testing;
235
236 EXPECT_CALL(*mock_loop, run())
237 .Times(AnyNumber());
238
239 mc::CompositorThread thread{std::move(mock_loop)};
240
241 run_signal.wait_for(std::chrono::seconds{1});
242 EXPECT_TRUE(run_signal.raised());
243
244 new_mock_loop();
245
246 EXPECT_FALSE(run_signal.raised());
247
248 EXPECT_CALL(*mock_loop, run())
249 .Times(AnyNumber());
250
251 thread.pause();
252 thread.run(std::move(mock_loop));
253
254 run_signal.wait_for(std::chrono::seconds{1});
255 EXPECT_TRUE(run_signal.raised());
256}
0257
=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-08-06 03:10:56 +0000
+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-08-30 16:57:50 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 * Copyright © 2013 Canonical Ltd.2 * Copyright © 2013-2014 Canonical Ltd.
3 *3 *
4 * This program is free software: you can redistribute it and/or modify4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as5 * it under the terms of the GNU General Public License version 3 as
@@ -13,11 +13,15 @@
13 * You should have received a copy of the GNU General Public License13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *15 *
16 * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>16 * Authored by:
17 * Alberto Aguirre <alberto.aguirre@canonical.com>
18 * Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */19 */
1820
19#include "src/server/compositor/multi_threaded_compositor.h"21#include "src/server/compositor/multi_threaded_compositor.h"
20#include "src/server/report/null_report_factory.h"22#include "src/server/report/null_report_factory.h"
23#include "src/server/compositor/compositor_thread.h"
24#include "src/server/compositor/compositor_thread_factory.h"
2125
22#include "mir/compositor/display_buffer_compositor.h"26#include "mir/compositor/display_buffer_compositor.h"
23#include "mir/compositor/scene.h"27#include "mir/compositor/scene.h"
@@ -31,6 +35,7 @@
31#include "mir_test_doubles/mock_compositor_report.h"35#include "mir_test_doubles/mock_compositor_report.h"
32#include "mir_test_doubles/mock_scene.h"36#include "mir_test_doubles/mock_scene.h"
33#include "mir_test_doubles/stub_scene.h"37#include "mir_test_doubles/stub_scene.h"
38#include "mir_test_doubles/mock_compositor_thread.h"
3439
35#include <boost/throw_exception.hpp>40#include <boost/throw_exception.hpp>
3641
@@ -42,6 +47,7 @@
4247
43#include <gmock/gmock.h>48#include <gmock/gmock.h>
44#include <gtest/gtest.h>49#include <gtest/gtest.h>
50#include "mir_test/gmock_fixes.h"
4551
46namespace mc = mir::compositor;52namespace mc = mir::compositor;
47namespace mg = mir::graphics;53namespace mg = mir::graphics;
@@ -65,6 +71,12 @@
65 f(db);71 f(db);
66 }72 }
6773
74 void new_buffers(unsigned int nbuffers)
75 {
76 buffers.clear();
77 buffers = std::vector<mtd::NullDisplayBuffer>{nbuffers};
78 }
79
68private:80private:
69 std::vector<mtd::NullDisplayBuffer> buffers;81 std::vector<mtd::NullDisplayBuffer> buffers;
70};82};
@@ -114,7 +126,7 @@
114 {126 {
115 {127 {
116 std::lock_guard<std::mutex> lock{observer_mutex};128 std::lock_guard<std::mutex> lock{observer_mutex};
117 129
118 // Any old event will do.130 // Any old event will do.
119 if (observer)131 if (observer)
120 observer->surfaces_reordered();132 observer->surfaces_reordered();
@@ -315,34 +327,66 @@
315 }327 }
316};328};
317329
318class ThreadNameDisplayBufferCompositorFactory : public mc::DisplayBufferCompositorFactory
319{
320public:
321 std::unique_ptr<mc::DisplayBufferCompositor> create_compositor_for(mg::DisplayBuffer&)
322 {
323 auto raw = new RecordingDisplayBufferCompositor{
324 [this]()
325 {
326 std::lock_guard<std::mutex> lock{thread_names_mutex};
327 thread_names.emplace_back(mt::current_thread_name());
328 }};
329 return std::unique_ptr<RecordingDisplayBufferCompositor>(raw);
330 }
331
332 size_t num_thread_names_gathered()
333 {
334 std::lock_guard<std::mutex> lock{thread_names_mutex};
335 return thread_names.size();
336 }
337
338 std::mutex thread_names_mutex;
339 std::vector<std::string> thread_names;
340};
341
342auto const null_report = mr::null_compositor_report();330auto const null_report = mr::null_compositor_report();
343unsigned int const composites_per_update{1};331unsigned int const composites_per_update{1};
344}332}
345333
334class CompositorThreadMockFactory : public mc::CompositorThreadFactory
335{
336public:
337 CompositorThreadMockFactory() : CompositorThreadMockFactory(
338 [](mtd::MockCompositorThread* t) {
339 //Can't use NiceMock on MockCompositorThread
340 using namespace testing;
341 EXPECT_CALL(*t, run_()).Times(AnyNumber());
342 EXPECT_CALL(*t, pause_()).Times(AnyNumber());
343 EXPECT_CALL(*t, destroyed());
344 })
345 {
346 }
347
348 CompositorThreadMockFactory(std::function<void(mtd::MockCompositorThread*)> notify_on_create)
349 : on_create{notify_on_create},
350 num_thread_deaths_{0}
351 {
352 using namespace testing;
353 ON_CALL(*this, create_thread(_))
354 .WillByDefault(Invoke(this, &CompositorThreadMockFactory::create_thread_));
355 }
356
357 void record_thread_death()
358 {
359 num_thread_deaths_++;
360 }
361
362 int num_thread_deaths() const
363 {
364 return num_thread_deaths_;
365 }
366
367 std::unique_ptr<mc::CompositorThread> create_compositor_thread_for(std::unique_ptr<mc::CompositorLoop> loop) override
368 {
369 return create_thread(loop);
370 }
371
372 std::unique_ptr<mc::CompositorThread> create_thread_(std::unique_ptr<mc::CompositorLoop>& loop)
373 {
374 using namespace testing;
375 auto mock_thread = new mtd::MockCompositorThread(loop);
376 ON_CALL(*mock_thread, destroyed())
377 .WillByDefault(Invoke(this, &CompositorThreadMockFactory::record_thread_death));
378
379 if (on_create)
380 on_create(mock_thread);
381 return std::unique_ptr<mc::CompositorThread>{mock_thread};
382 }
383
384 MOCK_METHOD1(create_thread, std::unique_ptr<mc::CompositorThread>(std::unique_ptr<mc::CompositorLoop>&));
385private:
386 std::function<void(mtd::MockCompositorThread*)> on_create;
387 int num_thread_deaths_;
388};
389
346TEST(MultiThreadedCompositor, compositing_happens_in_different_threads)390TEST(MultiThreadedCompositor, compositing_happens_in_different_threads)
347{391{
348 using namespace testing;392 using namespace testing;
@@ -352,7 +396,8 @@
352 auto display = std::make_shared<StubDisplay>(nbuffers);396 auto display = std::make_shared<StubDisplay>(nbuffers);
353 auto scene = std::make_shared<StubScene>();397 auto scene = std::make_shared<StubScene>();
354 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();398 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
355 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};399 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
400 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true};
356401
357 compositor.start();402 compositor.start();
358403
@@ -373,9 +418,11 @@
373 auto scene = std::make_shared<StubScene>();418 auto scene = std::make_shared<StubScene>();
374 auto db_compositor_factory =419 auto db_compositor_factory =
375 std::make_shared<RecordingDisplayBufferCompositorFactory>();420 std::make_shared<RecordingDisplayBufferCompositorFactory>();
421 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
376 auto mock_report = std::make_shared<mtd::MockCompositorReport>();422 auto mock_report = std::make_shared<mtd::MockCompositorReport>();
377 mc::MultiThreadedCompositor compositor{display, scene,423 mc::MultiThreadedCompositor compositor{display, scene,
378 db_compositor_factory,424 db_compositor_factory,
425 thread_factory,
379 mock_report,426 mock_report,
380 true};427 true};
381428
@@ -427,7 +474,8 @@
427 auto display = std::make_shared<StubDisplay>(nbuffers);474 auto display = std::make_shared<StubDisplay>(nbuffers);
428 auto scene = std::make_shared<StubScene>();475 auto scene = std::make_shared<StubScene>();
429 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();476 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
430 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};477 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
478 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true};
431479
432 // Verify we're actually starting at zero frames480 // Verify we're actually starting at zero frames
433 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0));481 EXPECT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0));
@@ -487,7 +535,8 @@
487 auto display = std::make_shared<StubDisplay>(nbuffers);535 auto display = std::make_shared<StubDisplay>(nbuffers);
488 auto scene = std::make_shared<StubScene>();536 auto scene = std::make_shared<StubScene>();
489 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();537 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
490 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, false};538 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
539 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, false};
491540
492 // Verify we're actually starting at zero frames541 // Verify we're actually starting at zero frames
493 ASSERT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0));542 ASSERT_TRUE(db_compositor_factory->check_record_count_for_each_buffer(nbuffers, 0, 0));
@@ -510,7 +559,8 @@
510 auto display = std::make_shared<StubDisplay>(nbuffers);559 auto display = std::make_shared<StubDisplay>(nbuffers);
511 auto scene = std::make_shared<StubScene>();560 auto scene = std::make_shared<StubScene>();
512 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();561 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();
513 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, false};562 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
563 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, false};
514564
515 compositor.start();565 compositor.start();
516566
@@ -545,7 +595,8 @@
545 auto display = std::make_shared<StubDisplay>(nbuffers);595 auto display = std::make_shared<StubDisplay>(nbuffers);
546 auto scene = std::make_shared<StubScene>();596 auto scene = std::make_shared<StubScene>();
547 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);597 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
548 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};598 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
599 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true};
549600
550 compositor.start();601 compositor.start();
551602
@@ -563,8 +614,10 @@
563614
564 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);615 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
565 auto scene = std::make_shared<StubScene>();616 auto scene = std::make_shared<StubScene>();
566 auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();617 auto db_compositor_factory =
567 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};618 std::make_shared<RecordingDisplayBufferCompositorFactory>();
619 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
620 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, thread_factory, null_report, true};
568621
569 display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf)622 display->for_each_mock_buffer([](mtd::MockDisplayBuffer& mock_buf)
570 {623 {
@@ -575,6 +628,10 @@
575 });628 });
576629
577 compositor.start();630 compositor.start();
631
632 while (!db_compositor_factory->enough_records_gathered(nbuffers, nbuffers))
633 scene->emit_change_event();
634
578 compositor.stop();635 compositor.stop();
579}636}
580637
@@ -586,6 +643,7 @@
586 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);643 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
587 auto mock_scene = std::make_shared<mtd::MockScene>();644 auto mock_scene = std::make_shared<mtd::MockScene>();
588 auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();645 auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
646 auto thread_factory = std::make_shared<mc::CompositorThreadFactory>();
589 auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>();647 auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>();
590648
591 EXPECT_CALL(*mock_report, started())649 EXPECT_CALL(*mock_report, started())
@@ -600,7 +658,7 @@
600 .Times(AtLeast(0))658 .Times(AtLeast(0))
601 .WillRepeatedly(Return(mc::SceneElementSequence{}));659 .WillRepeatedly(Return(mc::SceneElementSequence{}));
602660
603 mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, mock_report, true};661 mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, thread_factory, mock_report, true};
604662
605 compositor.start();663 compositor.start();
606 compositor.start();664 compositor.start();
@@ -608,76 +666,165 @@
608 compositor.stop();666 compositor.stop();
609}667}
610668
611TEST(MultiThreadedCompositor, cleans_up_after_throw_in_start)669TEST(MultiThreadedCompositor, no_threads_created_when_adding_scene_observer_throws)
612{670{
671 using namespace testing;
672
613 unsigned int const nbuffers{3};673 unsigned int const nbuffers{3};
614674 auto display = std::make_shared<StubDisplay>(nbuffers);
615 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
616 auto scene = std::make_shared<StubScene>();675 auto scene = std::make_shared<StubScene>();
617 auto db_compositor_factory = std::make_shared<RecordingDisplayBufferCompositorFactory>();676 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
618 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};677 auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>();
678
679 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true};
680
681 EXPECT_CALL(*mock_thread_factory, create_thread(_))
682 .Times(0);
619683
620 scene->throw_on_add_observer(true);684 scene->throw_on_add_observer(true);
621
622 EXPECT_THROW(compositor.start(), std::runtime_error);685 EXPECT_THROW(compositor.start(), std::runtime_error);
623
624 scene->throw_on_add_observer(false);686 scene->throw_on_add_observer(false);
625687}
626 /* No point in running the rest of the test if it throws again */688
627 ASSERT_NO_THROW(compositor.start());689TEST(MultiThreadedCompositor, cleans_up_allocated_threads_after_throw_during_next_thread_creation)
628690{
629 /* The minimum number of records here should be nbuffers *2, since we are checking for691 using namespace testing;
630 * presence of at least one additional rogue compositor thread per display buffer692
631 * However to avoid timing considerations like one good thread compositing the display buffer693 unsigned int const nbuffers{3};
632 * twice before the rogue thread gets a chance to, an arbitrary number of records are gathered694 auto display = std::make_shared<StubDisplay>(nbuffers);
633 */695 auto scene = std::make_shared<StubScene>();
634 unsigned int min_number_of_records = 100;696 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
635697
636 /* Timeout here in case the exception from setting the scene callback put the compositor698 auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) {
637 * in a bad state that did not allow it to composite (hence no records gathered)699 EXPECT_CALL(*t, pause_());
638 */700 EXPECT_CALL(*t, destroyed());
639 auto time_out = std::chrono::steady_clock::now() + std::chrono::seconds(1);701 };
640 while (!db_compositor_factory->enough_records_gathered(nbuffers, min_number_of_records) &&702 auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations);
641 std::chrono::steady_clock::now() <= time_out)703
704 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true};
705
706 EXPECT_CALL(*mock_thread_factory, create_thread(_))
707 .WillOnce(Invoke(mock_thread_factory.get(), &CompositorThreadMockFactory::create_thread_))
708 .WillOnce(Invoke(mock_thread_factory.get(), &CompositorThreadMockFactory::create_thread_))
709 .WillOnce(Throw(std::runtime_error("")));
710
711 EXPECT_THROW(compositor.start(), std::runtime_error);
712 EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(2));
713}
714
715TEST(MultiThreadedCompositor, recycles_threads)
716{
717 using namespace testing;
718
719 unsigned int const nbuffers{3};
720 auto display = std::make_shared<StubDisplay>(nbuffers);
721 auto scene = std::make_shared<StubScene>();
722 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
723
724 auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) {
725 //Run is only called when a thread has been recycled
726 EXPECT_CALL(*t, run_());
727 EXPECT_CALL(*t, destroyed());
728 EXPECT_CALL(*t, pause_())
729 .Times(2);
730 };
731 auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations);
732
733 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true};
734
735 EXPECT_CALL(*mock_thread_factory, create_thread(_))
736 .Times(nbuffers);
737
738 compositor.start();
739 compositor.stop();
740 compositor.start();
741 compositor.stop();
742
743 EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(0));
744}
745
746TEST(MultiThreadedCompositor, allocates_new_threads_for_new_display_buffers)
747{
748 using namespace testing;
749
750 unsigned int const nbuffers{3};
751 auto display = std::make_shared<StubDisplay>(nbuffers);
752 auto scene = std::make_shared<StubScene>();
753 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
754 auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>();
755
756 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true};
757
758 EXPECT_CALL(*mock_thread_factory, create_thread(_))
759 .Times(nbuffers*2);
760
761 compositor.start();
762 compositor.stop();
763
764 display->new_buffers(nbuffers);
765
766 compositor.start();
767 compositor.stop();
768}
769
770TEST(MultiThreadedCompositor, cleans_up_threads_for_unused_display_buffers_at_next_start)
771{
772 using namespace testing;
773
774 unsigned int const nbuffers{3};
775
776 auto display = std::make_shared<StubDisplay>(nbuffers);
777 auto scene = std::make_shared<StubScene>();
778 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
779
780 auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) {
781 EXPECT_CALL(*t, run_())
782 .Times(0);
783 EXPECT_CALL(*t, pause_());
784 EXPECT_CALL(*t, destroyed());
785 };
786 auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations);
787
788 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true};
789
790 EXPECT_CALL(*mock_thread_factory, create_thread(_))
791 .Times(nbuffers*2);
792
793 compositor.start();
794 compositor.stop();
795
796 display->new_buffers(nbuffers);
797
798 compositor.start();
799
800 EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(nbuffers));
801}
802
803TEST(MultiThreadedCompositor, destructor_cleans_up_threads)
804{
805 using namespace testing;
806
807 unsigned int const nbuffers{3};
808
809 auto display = std::make_shared<StubDisplay>(nbuffers);
810 auto scene = std::make_shared<StubScene>();
811 auto db_compositor_factory = std::make_shared<SurfaceUpdatingDisplayBufferCompositorFactory>(scene);
812
813 auto set_mock_thread_expectations = [&](mtd::MockCompositorThread* t) {
814 EXPECT_CALL(*t, run_())
815 .Times(0);
816 EXPECT_CALL(*t, pause_());
817 EXPECT_CALL(*t, destroyed());
818 };
819 auto mock_thread_factory = std::make_shared<CompositorThreadMockFactory>(set_mock_thread_expectations);
820
821 EXPECT_CALL(*mock_thread_factory, create_thread(_))
822 .Times(nbuffers);
823
642 {824 {
643 scene->emit_change_event();825 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, mock_thread_factory, null_report, true};
644 std::this_thread::yield();826 compositor.start();
645 }827 }
646828
647 /* Check expectation in case a timeout happened */829 EXPECT_THAT(mock_thread_factory->num_thread_deaths(), Eq(nbuffers));
648 EXPECT_TRUE(db_compositor_factory->enough_records_gathered(nbuffers, min_number_of_records));
649
650 compositor.stop();
651
652 /* Only one thread should be rendering each display buffer
653 * If the compositor failed to cleanup correctly more than one thread could be
654 * compositing the same display buffer
655 */
656 EXPECT_TRUE(db_compositor_factory->each_buffer_rendered_in_single_thread());
657}
658
659TEST(MultiThreadedCompositor, names_compositor_threads)
660{
661 using namespace testing;
662
663 unsigned int const nbuffers{3};
664
665 auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
666 auto scene = std::make_shared<StubScene>();
667 auto db_compositor_factory = std::make_shared<ThreadNameDisplayBufferCompositorFactory>();
668 mc::MultiThreadedCompositor compositor{display, scene, db_compositor_factory, null_report, true};
669
670 compositor.start();
671
672 unsigned int const min_number_of_thread_names = 10;
673
674 while (db_compositor_factory->num_thread_names_gathered() < min_number_of_thread_names)
675 scene->emit_change_event();
676
677 compositor.stop();
678
679 auto const& thread_names = db_compositor_factory->thread_names;
680
681 for (size_t i = 0; i < thread_names.size(); ++i)
682 EXPECT_THAT(thread_names[i], Eq("Mir/Comp")) << "i=" << i;
683}830}

Subscribers

People subscribed via source and target branches