Mir

Merge lp:~kdub/mir/compositor-double-start-stop into lp:mir

Proposed by Kevin DuBois
Status: Merged
Approved by: Daniel van Vugt
Approved revision: no longer in the source branch.
Merged at revision: 1342
Proposed branch: lp:~kdub/mir/compositor-double-start-stop
Merge into: lp:mir
Diff against target: 149 lines (+63/-2)
4 files modified
include/test/mir_test_doubles/mock_display_buffer.h (+8/-0)
src/server/compositor/multi_threaded_compositor.cpp (+18/-1)
src/server/compositor/multi_threaded_compositor.h (+4/-0)
tests/unit-tests/compositor/test_multi_threaded_compositor.cpp (+33/-1)
To merge this branch: bzr merge lp:~kdub/mir/compositor-double-start-stop
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Chris Halse Rogers for now Approve
Alan Griffiths Needs Fixing
Daniel van Vugt Approve
Alexandros Frantzis (community) Needs Information
Andreas Pokorny (community) Needs Information
Review via email: mp+201242@code.launchpad.net

Commit message

compositor: ignore double requests to start or stop the MultiThreadedCompositor

Description of the change

compositor: ignore double requests to start or stop the MultiThreadedCompositor.

guard against this scenario, because sometimes unity-mir calls this twice in a row, and this class doesn't handle that very well. (we're still working out power policy, so lets guard against mir errors here)

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
Andreas Pokorny (andreas-pokorny) wrote :

Is that case something we should care about?

TEST(MultiThreadedCompositor, double_threaded_start_ignored)
{
    unsigned int const nbuffers{3};
    auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
    auto mock_scene = std::make_shared<MockScene>();
    auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
    auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>();
    EXPECT_CALL(*mock_report, started())
        .Times(1);

    mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, mock_report};

    std::thread{[&](){ compositor.start()} };
    std::thread{[&](){ compositor.start()} };
}

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

Looks good.

Just noting "geometry::Rectangle{{0,0},{0,0}}" is probably better written as "geometry::Rectangle()".

review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

I'd expect multiple calls to ::MultiThreadedCompositor::start() to be a precondition violation.

Do we really need to handle what sounds like a bug in unity-mir?

If so, is ignoring it really better than aborting?

review: Needs Information
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

Since code external to mir is now manipulating the compositor I have concerns about coordinating requests and events involving the compositor. MultiThreadedCompositor doesn't handle multiple starts/stops, because in many cases internal synchronization is not enough; starts/stops need to be coordinated at a higher level and are therefore serialized through our main loop.

Allowing arbitrary code to start/stop the compositor is bound to break Mir. Consider, for example, the dire consequences of restarting the compositor while it is disabled for a display reconfiguration event.

I am not familiar with the code that needs to stop/start the compositor in unity-mir, but we probably need to provide a way (interface) for unity-mir to do what it wants, while we do the right thing internally (go through our main loop).

"Needs Discussion"

review: Needs Information
Revision history for this message
Kevin DuBois (kdub) wrote :

I agree that calling start twice is a violation of our preconditions, and that exposing the compositor start/stop to unity-mir might break other things in Mir. We should expose something simpler.

This is what unity-mir is doing at the moment though:
http://bazaar.launchpad.net/~mir-team/unity-mir/trunk/view/head:/src/unity-mir/dbusscreen.cpp

And (for reasons I don't know) sometimes start/stop are called twice, which generates "error during eglMakeCurrent", which mir gets blamed for. Perhaps I should change this to throw if start or stop is called twice?

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Andreas, Alexandros, Kevin and I are all agreed that calling start twice is a precondition violation, so lets take that as our premise.

Ideally preconditions are the responsibility of the calling code so my first inclination would be to fix that.

But as existing code is hitting this condition then a better diagnostic is the least we can do. We can certainly provide better information than "error during eglMakeCurrent"!

In DBusScreen::setScreenPowerMode() it looks as though start() will be called whenever 'mode == "on"' - regardless of the existing mode.

As Alexandros comments though it would be good to understand what the client code is intending - if the compositor really needs to be stopped when reconfiguring the display then it probably shouldn't be client code responsibility.

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

I think we're falling on the side of pedantism over practicality. I'd say any interface with "start" and "stop" should do this internal state check, for robustness in two situations:
  (a) The caller doesn't know if it's started yet, but needs it started; and
  (b) The caller doesn't know if it's stopped yet, but needs it stopped.

For the caller to guarantee "preconditions" means each caller must track or query the state of the object being started/stopped, which is dangerous and redundant.

review: Approve
Revision history for this message
Kevin DuBois (kdub) wrote :

A good compromise seems to be to keep the changes to make it more robust and warn via the compositor log when this happens. Will work on that tomorrow.

Revision history for this message
Gerry Boland (gerboland) wrote :

If unity-mir was the sole consumer of the Compositor::start and Compositor::stop, then it could save the compositor running state itself. However I don't believe that is the case, is it?

If you supplied a "running" method to check for the compositor thread state, then unity-mir would have sufficient information to make the right decision.

However I do also believe a library should try to recover gracefully from mis-use whenever possible. Calling start() twice shouldn't crash.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

> If unity-mir was the sole consumer of the Compositor::start and
> Compositor::stop, then it could save the compositor running state itself.
> However I don't believe that is the case, is it?

I'm *still* not convinced that unity-mir should be calling Compositor::start()/stop() in this case it smells of being a workaround for a Mir bug.

If justified, it would be possible for unity-mir to track the state correctly by overriding Compositor::start()/stop() and only calling the base version when appropriate.

> If you supplied a "running" method to check for the compositor thread state,
> then unity-mir would have sufficient information to make the right decision.

No it couldn't: start()/stop() could be called on another thread after running() returned.

> However I do also believe a library should try to recover gracefully from mis-
> use whenever possible.

I think that a clearly diagnosable and immediate crash is preferable to ignoring errors only to fail in an obscure way later.

> Calling start() twice shouldn't crash.

That is a matter on which reasonable minds can (and do) differ. But it would be moot if there were no need to call it in USC or unity-mir.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

If, for the sake of moving forward, we accept the premise of this MP that we can have multiple callers of start() and stop() then we have to allow for the calls to occur on separate threads.

I.e. we need to make these functions thread safe.

review: Needs Fixing
Revision history for this message
Kevin DuBois (kdub) wrote :

I don't think this should land in the current form... we should provide unity mir with an easy lever they can push to stop and start the system without having to worry about stopping/starting the compositor and blanking/unblanking the screen. I'll move this to 'wip'

Revision history for this message
Kevin DuBois (kdub) wrote :

> I don't think this should land in the current form... we should provide unity
> mir with an easy lever they can push to stop and start the system without
> having to worry about stopping/starting the compositor and blanking/unblanking
> the screen. I'll move this to 'wip'

shortly after saying this, I have to stick my foot in my mouth because this averts a start-up bug for USC.

In investigating the problem that mterry was having with U-S-C today, it turns out what was going on was that we started the compositor when unity starts, and then later powerd will send us the first power on signal, and ignoring double-on calls averts the crash.

So I'll re-propose for now as a band-aid. The proper solution is to give USC/unity8 a class they can tinker with to turn the display server on and off.

Revision history for this message
Kevin DuBois (kdub) wrote :

> I.e. we need to make these functions thread safe.

should be threadsafe

Revision history for this message
Chris Halse Rogers (raof) wrote :

I'm happy to see this land now; it seems to be a pretty critical fix for our downstreams.

That said, this should be marked as technical debt - I'd suggest a comment on the test saying that it's testing temporary behaviour, and a TODO in start()/stop()?

review: Approve (for now)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/test/mir_test_doubles/mock_display_buffer.h'
2--- include/test/mir_test_doubles/mock_display_buffer.h 2014-01-17 19:42:09 +0000
3+++ include/test/mir_test_doubles/mock_display_buffer.h 2014-01-22 00:23:56 +0000
4@@ -33,6 +33,14 @@
5 class MockDisplayBuffer : public graphics::DisplayBuffer
6 {
7 public:
8+ MockDisplayBuffer()
9+ {
10+ using namespace testing;
11+ ON_CALL(*this, can_bypass())
12+ .WillByDefault(Return(false));
13+ ON_CALL(*this, view_area())
14+ .WillByDefault(Return(geometry::Rectangle{{0,0},{0,0}}));
15+ }
16 MOCK_CONST_METHOD0(view_area, geometry::Rectangle());
17 MOCK_METHOD0(make_current, void());
18 MOCK_METHOD0(release_current, void());
19
20=== modified file 'src/server/compositor/multi_threaded_compositor.cpp'
21--- src/server/compositor/multi_threaded_compositor.cpp 2014-01-13 06:12:33 +0000
22+++ src/server/compositor/multi_threaded_compositor.cpp 2014-01-22 00:23:56 +0000
23@@ -152,7 +152,8 @@
24 : display{display},
25 scene{scene},
26 display_buffer_compositor_factory{db_compositor_factory},
27- report{compositor_report}
28+ report{compositor_report},
29+ started{false}
30 {
31 }
32
33@@ -163,6 +164,12 @@
34
35 void mc::MultiThreadedCompositor::start()
36 {
37+ std::unique_lock<std::mutex> lk(started_guard);
38+ if (started)
39+ {
40+ return;
41+ }
42+
43 report->started();
44
45 /* Start the compositing threads */
46@@ -186,10 +193,18 @@
47 /* First render */
48 for (auto& f : thread_functors)
49 f->schedule_compositing();
50+
51+ started = true;
52 }
53
54 void mc::MultiThreadedCompositor::stop()
55 {
56+ std::unique_lock<std::mutex> lk(started_guard);
57+ if (!started)
58+ {
59+ return;
60+ }
61+
62 scene->set_change_callback([]{});
63
64 for (auto& f : thread_functors)
65@@ -202,4 +217,6 @@
66 threads.clear();
67
68 report->stopped();
69+
70+ started = false;
71 }
72
73=== modified file 'src/server/compositor/multi_threaded_compositor.h'
74--- src/server/compositor/multi_threaded_compositor.h 2014-01-13 06:12:33 +0000
75+++ src/server/compositor/multi_threaded_compositor.h 2014-01-22 00:23:56 +0000
76@@ -21,6 +21,7 @@
77
78 #include "mir/compositor/compositor.h"
79
80+#include <mutex>
81 #include <memory>
82 #include <vector>
83 #include <thread>
84@@ -61,6 +62,9 @@
85
86 std::vector<std::unique_ptr<CompositingFunctor>> thread_functors;
87 std::vector<std::thread> threads;
88+
89+ std::mutex started_guard;
90+ bool started;
91 };
92
93 }
94
95=== modified file 'tests/unit-tests/compositor/test_multi_threaded_compositor.cpp'
96--- tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-01-13 06:12:33 +0000
97+++ tests/unit-tests/compositor/test_multi_threaded_compositor.cpp 2014-01-22 00:23:56 +0000
98@@ -76,7 +76,7 @@
99 }
100
101 private:
102- std::vector<mtd::MockDisplayBuffer> buffers;
103+ std::vector<testing::NiceMock<mtd::MockDisplayBuffer>> buffers;
104 };
105
106 class StubScene : public mc::Scene
107@@ -115,6 +115,16 @@
108 std::mutex callback_mutex;
109 };
110
111+class MockScene : public mc::Scene
112+{
113+public:
114+ MOCK_METHOD2(for_each_if, void(mc::FilterForScene&, mc::OperatorForScene&));
115+ MOCK_METHOD2(reverse_for_each_if, void(mc::FilterForScene&, mc::OperatorForScene&));
116+ MOCK_METHOD1(set_change_callback, void(std::function<void()> const&));
117+ MOCK_METHOD0(lock, void());
118+ MOCK_METHOD0(unlock, void());
119+};
120+
121 class RecordingDisplayBufferCompositor : public mc::DisplayBufferCompositor
122 {
123 public:
124@@ -477,3 +487,25 @@
125 compositor.start();
126 compositor.stop();
127 }
128+
129+TEST(MultiThreadedCompositor, double_start_or_stop_ignored)
130+{
131+ unsigned int const nbuffers{3};
132+ auto display = std::make_shared<StubDisplayWithMockBuffers>(nbuffers);
133+ auto mock_scene = std::make_shared<MockScene>();
134+ auto db_compositor_factory = std::make_shared<NullDisplayBufferCompositorFactory>();
135+ auto mock_report = std::make_shared<testing::NiceMock<mtd::MockCompositorReport>>();
136+ EXPECT_CALL(*mock_report, started())
137+ .Times(1);
138+ EXPECT_CALL(*mock_report, stopped())
139+ .Times(1);
140+ EXPECT_CALL(*mock_scene, set_change_callback(testing::_))
141+ .Times(2);
142+
143+ mc::MultiThreadedCompositor compositor{display, mock_scene, db_compositor_factory, mock_report};
144+
145+ compositor.start();
146+ compositor.start();
147+ compositor.stop();
148+ compositor.stop();
149+}

Subscribers

People subscribed via source and target branches