Merge lp:~afrantzis/unity-system-compositor/fix-1491566-deadlock into lp:unity-system-compositor

Proposed by Alexandros Frantzis
Status: Merged
Approved by: Alexandros Frantzis
Approved revision: 254
Merged at revision: 253
Proposed branch: lp:~afrantzis/unity-system-compositor/fix-1491566-deadlock
Merge into: lp:unity-system-compositor
Diff against target: 500 lines (+366/-15)
5 files modified
src/mir_screen.cpp (+47/-4)
src/mir_screen.h (+9/-2)
tests/integration-tests/CMakeLists.txt (+1/-0)
tests/integration-tests/test_deadlock_lp1491566.cpp (+283/-0)
tests/unit-tests/advanceable_timer.cpp (+26/-9)
To merge this branch: bzr merge lp:~afrantzis/unity-system-compositor/fix-1491566-deadlock
Reviewer Review Type Date Requested Status
Andreas Pokorny (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+272013@code.launchpad.net

Commit message

Fix deadlock by ensuring our alarm handlers acquire locks in the correct order

Description of the change

Fix deadlock by ensuring our alarm handlers acquire locks in the correct order

This MP adds the before_power_off_alarm and before_dimmer_alarm virtual functions which do nothing in production. They are used only in tests for in order to allow us to deterministically reproduce the deadlock. The runtime cost in production is minimal and the worth we get from protection against regressions of this bug is significant.

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 :

Do you really need the before_* methods in MirScreen?
You could simply override the method and call a before function<> inside the test setup.

Why does that fix the issue? The lock ordering is still the same now?

review: Needs Information
254. By Alexandros Frantzis

Make MirScreen alarm handlers overridable for test purposes

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

> Do you really need the before_* methods in MirScreen?
> You could simply override the method and call a before function<> inside the test setup.

Ack. Changed!

> Why does that fix the issue? The lock ordering is still the same now?

The LockableCallback::lock() and unlock() functions are called before acquiring the internal alarm lock ([1]). So when the alarm callback occurs we now get [lock MirScreen], [lock Alarm], [dispatch callback] instead of [lock Alarm], [dispatch callback], [lock MirScreen] we had previously.

[1] http://bazaar.launchpad.net/~mir-team/mir/development-branch/view/head:/src/server/glib_main_loop_sources.cpp#L308

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

> The LockableCallback::lock() and unlock() functions are called before

s/and unlock()//

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 :

Happy with the change.. I wonder if it would be simpler code if we had Dispatchables there and execute them single threaded. But thats a bigger rework

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/mir_screen.cpp'
--- src/mir_screen.cpp 2015-08-24 18:14:36 +0000
+++ src/mir_screen.cpp 2015-09-23 20:13:49 +0000
@@ -18,6 +18,7 @@
18#include "clock.h"18#include "clock.h"
1919
20#include <mir/main_loop.h>20#include <mir/main_loop.h>
21#include <mir/lockable_callback.h>
21#include <mir/time/alarm_factory.h>22#include <mir/time/alarm_factory.h>
22#include <mir/compositor/compositor.h>23#include <mir/compositor/compositor.h>
23#include <mir/graphics/display.h>24#include <mir/graphics/display.h>
@@ -32,6 +33,50 @@
32namespace mi = mir::input;33namespace mi = mir::input;
33namespace mg = mir::graphics;34namespace mg = mir::graphics;
3435
36class usc::MirScreen::LockableCallback : public mir::LockableCallback
37{
38public:
39 LockableCallback(MirScreen* mir_screen)
40 : mir_screen{mir_screen}
41 {
42 }
43
44 void lock() override
45 {
46 guard_lock = std::unique_lock<std::mutex>{mir_screen->guard};
47 }
48
49 void unlock() override
50 {
51 if (guard_lock.owns_lock())
52 guard_lock.unlock();
53 }
54
55protected:
56 MirScreen* const mir_screen;
57 std::unique_lock<std::mutex> guard_lock;
58};
59
60class usc::MirScreen::PowerOffLockableCallback : public usc::MirScreen::LockableCallback
61{
62 using usc::MirScreen::LockableCallback::LockableCallback;
63
64 void operator()() override
65 {
66 mir_screen->power_off_alarm_notification();
67 }
68};
69
70class usc::MirScreen::DimmerLockableCallback : public usc::MirScreen::LockableCallback
71{
72 using usc::MirScreen::LockableCallback::LockableCallback;
73
74 void operator()() override
75 {
76 mir_screen->dimmer_alarm_notification();
77 }
78};
79
35usc::MirScreen::MirScreen(80usc::MirScreen::MirScreen(
36 std::shared_ptr<usc::ScreenHardware> const& screen_hardware,81 std::shared_ptr<usc::ScreenHardware> const& screen_hardware,
37 std::shared_ptr<mir::compositor::Compositor> const& compositor,82 std::shared_ptr<mir::compositor::Compositor> const& compositor,
@@ -48,9 +93,9 @@
48 alarm_factory{alarm_factory},93 alarm_factory{alarm_factory},
49 clock{clock},94 clock{clock},
50 power_off_alarm{alarm_factory->create_alarm(95 power_off_alarm{alarm_factory->create_alarm(
51 std::bind(&usc::MirScreen::power_off_alarm_notification, this))},96 std::make_shared<PowerOffLockableCallback>(this))},
52 dimmer_alarm{alarm_factory->create_alarm(97 dimmer_alarm{alarm_factory->create_alarm(
53 std::bind(&usc::MirScreen::dimmer_alarm_notification, this))},98 std::make_shared<DimmerLockableCallback>(this))},
54 inactivity_timeouts(inactivity_timeouts),99 inactivity_timeouts(inactivity_timeouts),
55 notification_timeouts(notification_timeouts),100 notification_timeouts(notification_timeouts),
56 current_power_mode{MirPowerMode::mir_power_mode_on},101 current_power_mode{MirPowerMode::mir_power_mode_on},
@@ -313,14 +358,12 @@
313358
314void usc::MirScreen::power_off_alarm_notification()359void usc::MirScreen::power_off_alarm_notification()
315{360{
316 std::lock_guard<std::mutex> lock{guard};
317 configure_display_l(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::inactivity);361 configure_display_l(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::inactivity);
318 next_power_off = {};362 next_power_off = {};
319}363}
320364
321void usc::MirScreen::dimmer_alarm_notification()365void usc::MirScreen::dimmer_alarm_notification()
322{366{
323 std::lock_guard<std::mutex> lock{guard};
324 screen_hardware->set_dim_backlight();367 screen_hardware->set_dim_backlight();
325 next_dimming = {};368 next_dimming = {};
326}369}
327370
=== modified file 'src/mir_screen.h'
--- src/mir_screen.h 2015-07-17 12:43:46 +0000
+++ src/mir_screen.h 2015-09-23 20:13:49 +0000
@@ -73,7 +73,16 @@
73 void register_power_state_change_handler(73 void register_power_state_change_handler(
74 PowerStateChangeHandler const& power_state_change_handler) override;74 PowerStateChangeHandler const& power_state_change_handler) override;
7575
76protected:
77 // These are protected virtual because we need to override them in tests
78 virtual void power_off_alarm_notification();
79 virtual void dimmer_alarm_notification();
80
76private:81private:
82 class LockableCallback;
83 class PowerOffLockableCallback;
84 class DimmerLockableCallback;
85
77 void set_screen_power_mode_l(MirPowerMode mode, PowerStateChangeReason reason);86 void set_screen_power_mode_l(MirPowerMode mode, PowerStateChangeReason reason);
78 void configure_display_l(MirPowerMode mode, PowerStateChangeReason reason);87 void configure_display_l(MirPowerMode mode, PowerStateChangeReason reason);
7988
@@ -84,8 +93,6 @@
84 Timeouts timeouts_for(PowerStateChangeReason reason);93 Timeouts timeouts_for(PowerStateChangeReason reason);
85 bool is_screen_change_allowed(MirPowerMode mode, PowerStateChangeReason reason);94 bool is_screen_change_allowed(MirPowerMode mode, PowerStateChangeReason reason);
8695
87 void power_off_alarm_notification();
88 void dimmer_alarm_notification();
89 void long_press_alarm_notification();96 void long_press_alarm_notification();
9097
91 std::shared_ptr<usc::ScreenHardware> const screen_hardware;98 std::shared_ptr<usc::ScreenHardware> const screen_hardware;
9299
=== modified file 'tests/integration-tests/CMakeLists.txt'
--- tests/integration-tests/CMakeLists.txt 2015-08-26 08:00:27 +0000
+++ tests/integration-tests/CMakeLists.txt 2015-09-23 20:13:49 +0000
@@ -38,6 +38,7 @@
38 test_unity_input_service.cpp38 test_unity_input_service.cpp
39 test_external_spinner.cpp39 test_external_spinner.cpp
40 test_powerd_mediator.cpp40 test_powerd_mediator.cpp
41 test_deadlock_lp1491566.cpp
41)42)
4243
43target_link_libraries(44target_link_libraries(
4445
=== added file 'tests/integration-tests/test_deadlock_lp1491566.cpp'
--- tests/integration-tests/test_deadlock_lp1491566.cpp 1970-01-01 00:00:00 +0000
+++ tests/integration-tests/test_deadlock_lp1491566.cpp 2015-09-23 20:13:49 +0000
@@ -0,0 +1,283 @@
1/*
2 * Copyright © 2015 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: Alexandros Frantzis <alexandros.frantzis@canonical.com>
17 */
18
19#include "src/server.h"
20#include "src/mir_screen.h"
21#include "src/screen_hardware.h"
22#include "src/power_state_change_reason.h"
23#include "spin_wait.h"
24
25#include <mir/compositor/compositor.h>
26#include <mir/main_loop.h>
27#include <mir/graphics/display.h>
28#include <mir/graphics/display_configuration.h>
29#include <mir/graphics/gl_context.h>
30#include <mir/input/touch_visualizer.h>
31
32#include <gtest/gtest.h>
33#include <gmock/gmock.h>
34
35#include <thread>
36#include <future>
37
38namespace mg = mir::graphics;
39
40namespace
41{
42
43struct NullCompositor : mir::compositor::Compositor
44{
45 void start() override {}
46 void stop() override {}
47};
48
49struct StubDisplayConfiguration : mg::DisplayConfiguration
50{
51 StubDisplayConfiguration()
52 {
53 conf_output.power_mode = MirPowerMode::mir_power_mode_on;
54 }
55
56 void for_each_card(std::function<void(mg::DisplayConfigurationCard const&)> f) const override
57 {
58 }
59
60 void for_each_output(std::function<void(mg::DisplayConfigurationOutput const&)> f) const override
61 {
62 f(conf_output);
63 }
64
65 void for_each_output(std::function<void(mg::UserDisplayConfigurationOutput&)> f)
66 {
67 mg::UserDisplayConfigurationOutput user{conf_output};
68 f(user);
69 }
70
71 mg::DisplayConfigurationOutput conf_output;
72};
73
74struct StubDisplay : mg::Display
75{
76 void for_each_display_sync_group(std::function<void(mg::DisplaySyncGroup&)> const& f) override
77 {
78 }
79
80 std::unique_ptr<mg::DisplayConfiguration> configuration() const override
81 {
82 return std::unique_ptr<mg::DisplayConfiguration>{new StubDisplayConfiguration{}};
83 }
84
85 void configure(mg::DisplayConfiguration const&) override {}
86
87 void register_configuration_change_handler(
88 mg::EventHandlerRegister& ,
89 mg::DisplayConfigurationChangeHandler const& ) override
90 {
91 }
92
93 void register_pause_resume_handlers(
94 mg::EventHandlerRegister&,
95 mg::DisplayPauseHandler const&,
96 mg::DisplayResumeHandler const&) override
97 {
98 }
99
100 void pause() override {}
101 void resume() override {}
102
103 std::shared_ptr<mg::Cursor> create_hardware_cursor(
104 std::shared_ptr<mg::CursorImage> const&) override
105 {
106 return{};
107 }
108
109 std::unique_ptr<mg::GLContext> create_gl_context() override
110 {
111 return std::unique_ptr<mg::GLContext>{};
112 }
113};
114
115struct StubScreenHardware : usc::ScreenHardware
116{
117 void set_dim_backlight() override {}
118 void set_normal_backlight() override {}
119 void turn_off_backlight() override {}
120
121 void change_backlight_values(int dim_brightness, int normal_brightness) override {}
122
123 void allow_suspend() override {}
124 void disable_suspend() override {}
125
126 void enable_auto_brightness(bool) override {}
127 bool auto_brightness_supported() override { return true; }
128 void set_brightness(int) override {}
129 int min_brightness() override { return 100; }
130 int max_brightness() override { return 0; }
131
132 void enable_proximity(bool) override { }
133};
134
135struct NullTouchVisualizer : mir::input::TouchVisualizer
136{
137 void enable() override {}
138 void disable() override {}
139 void visualize_touches(std::vector<Spot> const& touches) override {}
140};
141
142class TestMirScreen : public usc::MirScreen
143{
144public:
145 using usc::MirScreen::MirScreen;
146
147 void dimmer_alarm_notification() override
148 {
149 before_dimmer_alarm_func();
150 usc::MirScreen::dimmer_alarm_notification();
151 }
152
153 void power_off_alarm_notification() override
154 {
155 before_power_off_alarm_func();
156 usc::MirScreen::power_off_alarm_notification();
157 }
158
159 std::function<void()> before_dimmer_alarm_func = []{};
160 std::function<void()> before_power_off_alarm_func = []{};
161};
162
163struct DeadlockLP1491566 : public testing::Test
164{
165 DeadlockLP1491566()
166 {
167 main_loop_thread = std::thread{[this] { main_loop->run(); }};
168 }
169
170 ~DeadlockLP1491566()
171 {
172 main_loop->stop();
173 main_loop_thread.join();
174 }
175
176 std::future<void> async_set_screen_power_mode()
177 {
178 return std::async(
179 std::launch::async,
180 [this]
181 {
182 mir_screen.set_screen_power_mode(
183 MirPowerMode::mir_power_mode_on,
184 PowerStateChangeReason::power_key);
185 });
186 }
187
188 void schedule_inactivity_handlers()
189 {
190 // We set the screen power mode to on, which will
191 // schedule inactivity handlers
192 mir_screen.set_screen_power_mode(
193 MirPowerMode::mir_power_mode_on,
194 PowerStateChangeReason::power_key);
195 }
196
197 void wait_for_async_operation(std::future<void>& future)
198 {
199 if (!usc::test::spin_wait_for_condition_or_timeout(
200 [&future] { return future.valid(); },
201 std::chrono::seconds{3}))
202 {
203 throw std::runtime_error{"Future is not valid!"};
204 }
205
206 if (future.wait_for(std::chrono::seconds{3}) != std::future_status::ready)
207 {
208 std::cerr << "Deadlock detected. Aborting." << std::endl;
209 abort();
210 }
211 }
212
213 char *argv[1] = {nullptr};
214 usc::Server server{0, argv};
215 std::shared_ptr<mir::MainLoop> const main_loop{server.the_main_loop()};
216 std::chrono::milliseconds const power_off_timeout{200};
217 std::chrono::milliseconds const dimmer_timeout{100};
218
219 TestMirScreen mir_screen{
220 std::make_shared<StubScreenHardware>(),
221 std::make_shared<NullCompositor>(),
222 std::make_shared<StubDisplay>(),
223 std::make_shared<NullTouchVisualizer>(),
224 main_loop,
225 server.the_clock(),
226 {power_off_timeout, dimmer_timeout},
227 {power_off_timeout, dimmer_timeout}};
228
229 std::thread main_loop_thread;
230};
231
232}
233
234// The following tests reproduce the situation we are seeing in bug 1491566,
235// where a set_screen_power_mode request tries to run concurrently with either
236// the dimmer or power-off timeout handlers. The deadlock is caused by two
237// threads trying to acquire the internal alarm lock and the MirScreen lock in
238// opposite orders.
239//
240// To reproduce the race in these tests, we need to run the
241// set_screen_power_mode request just before the timeout handlers are invoked,
242// while the thread invoking the timeout handlers holds the internal alarm
243// lock, but before it has acquired the MirScreen lock. The thread calling
244// set_screen_power_mode will then first acquire the MirScreen lock and then
245// try to acquire the internal alarm clock and block. The timeout handler
246// thread will eventually resume and try to get the MirScreen lock, leading to
247// a deadlock.
248
249TEST_F(DeadlockLP1491566, between_dimmer_handler_and_screen_power_mode_request_is_averted)
250{
251 std::future<void> future;
252
253 mir_screen.before_dimmer_alarm_func =
254 [this, &future]
255 {
256 future = async_set_screen_power_mode();
257 // Wait a bit for async operation to get processed
258 std::this_thread::sleep_for(
259 std::chrono::milliseconds{500});
260 };
261
262 schedule_inactivity_handlers();
263
264 wait_for_async_operation(future);
265}
266
267TEST_F(DeadlockLP1491566, between_power_off_handler_and_screen_power_mode_request_is_averted)
268{
269 std::future<void> future;
270
271 mir_screen.before_power_off_alarm_func =
272 [this, &future]
273 {
274 future = async_set_screen_power_mode();
275 // Wait a bit for async operation to get processed
276 std::this_thread::sleep_for(
277 std::chrono::milliseconds{500});
278 };
279
280 schedule_inactivity_handlers();
281
282 wait_for_async_operation(future);
283}
0284
=== modified file 'tests/unit-tests/advanceable_timer.cpp'
--- tests/unit-tests/advanceable_timer.cpp 2015-07-15 13:48:30 +0000
+++ tests/unit-tests/advanceable_timer.cpp 2015-09-23 20:13:49 +0000
@@ -17,6 +17,7 @@
17 */17 */
1818
19#include "advanceable_timer.h"19#include "advanceable_timer.h"
20#include <mir/lockable_callback.h>
20#include <algorithm>21#include <algorithm>
2122
22namespace23namespace
@@ -59,7 +60,7 @@
59{60{
60 AdvanceableAlarm(61 AdvanceableAlarm(
61 mir::time::Timestamp now,62 mir::time::Timestamp now,
62 std::function<void(void)> const& callback)63 std::shared_ptr<mir::LockableCallback> const& callback)
63 : now{now}, callback{callback}64 : now{now}, callback{callback}
64 {65 {
65 }66 }
@@ -101,7 +102,9 @@
101 {102 {
102 state_ = mir::time::Alarm::triggered;103 state_ = mir::time::Alarm::triggered;
103 lock.unlock();104 lock.unlock();
104 callback();105 callback->lock();
106 (*callback)();
107 callback->unlock();
105 }108 }
106 }109 }
107110
@@ -113,7 +116,7 @@
113116
114private:117private:
115 mir::time::Timestamp now;118 mir::time::Timestamp now;
116 std::function<void()> const callback;119 std::shared_ptr<mir::LockableCallback> const callback;
117 mir::time::Timestamp next_trigger_;120 mir::time::Timestamp next_trigger_;
118 mutable std::mutex mutex;121 mutable std::mutex mutex;
119 mir::time::Alarm::State state_ = mir::time::Alarm::triggered;122 mir::time::Alarm::State state_ = mir::time::Alarm::triggered;
@@ -123,6 +126,26 @@
123std::unique_ptr<mir::time::Alarm> AdvanceableTimer::create_alarm(126std::unique_ptr<mir::time::Alarm> AdvanceableTimer::create_alarm(
124 std::function<void()> const& callback)127 std::function<void()> const& callback)
125{128{
129 struct SimpleLockableCallback : public mir::LockableCallback
130 {
131 SimpleLockableCallback(std::function<void()> const& callback)
132 : callback{callback}
133 {
134 }
135
136 void operator()() override { callback(); }
137 void lock() override {}
138 void unlock() override {}
139
140 std::function<void()> const callback;
141 };
142
143 return create_alarm(std::make_shared<SimpleLockableCallback>(callback));
144}
145
146std::unique_ptr<mir::time::Alarm> AdvanceableTimer::create_alarm(
147 std::shared_ptr<mir::LockableCallback> const& callback)
148{
126 auto const adv_alarm =149 auto const adv_alarm =
127 std::make_shared<detail::AdvanceableAlarm>(now(), callback);150 std::make_shared<detail::AdvanceableAlarm>(now(), callback);
128151
@@ -131,12 +154,6 @@
131 return std::unique_ptr<AlarmWrapper>(new AlarmWrapper{adv_alarm});154 return std::unique_ptr<AlarmWrapper>(new AlarmWrapper{adv_alarm});
132}155}
133156
134std::unique_ptr<mir::time::Alarm> AdvanceableTimer::create_alarm(
135 std::shared_ptr<mir::LockableCallback> const&)
136{
137 throw std::runtime_error("Not implemented");
138}
139
140void AdvanceableTimer::advance_by(std::chrono::milliseconds advance)157void AdvanceableTimer::advance_by(std::chrono::milliseconds advance)
141{158{
142 {159 {

Subscribers

People subscribed via source and target branches