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
1=== modified file 'src/mir_screen.cpp'
2--- src/mir_screen.cpp 2015-08-24 18:14:36 +0000
3+++ src/mir_screen.cpp 2015-09-23 20:13:49 +0000
4@@ -18,6 +18,7 @@
5 #include "clock.h"
6
7 #include <mir/main_loop.h>
8+#include <mir/lockable_callback.h>
9 #include <mir/time/alarm_factory.h>
10 #include <mir/compositor/compositor.h>
11 #include <mir/graphics/display.h>
12@@ -32,6 +33,50 @@
13 namespace mi = mir::input;
14 namespace mg = mir::graphics;
15
16+class usc::MirScreen::LockableCallback : public mir::LockableCallback
17+{
18+public:
19+ LockableCallback(MirScreen* mir_screen)
20+ : mir_screen{mir_screen}
21+ {
22+ }
23+
24+ void lock() override
25+ {
26+ guard_lock = std::unique_lock<std::mutex>{mir_screen->guard};
27+ }
28+
29+ void unlock() override
30+ {
31+ if (guard_lock.owns_lock())
32+ guard_lock.unlock();
33+ }
34+
35+protected:
36+ MirScreen* const mir_screen;
37+ std::unique_lock<std::mutex> guard_lock;
38+};
39+
40+class usc::MirScreen::PowerOffLockableCallback : public usc::MirScreen::LockableCallback
41+{
42+ using usc::MirScreen::LockableCallback::LockableCallback;
43+
44+ void operator()() override
45+ {
46+ mir_screen->power_off_alarm_notification();
47+ }
48+};
49+
50+class usc::MirScreen::DimmerLockableCallback : public usc::MirScreen::LockableCallback
51+{
52+ using usc::MirScreen::LockableCallback::LockableCallback;
53+
54+ void operator()() override
55+ {
56+ mir_screen->dimmer_alarm_notification();
57+ }
58+};
59+
60 usc::MirScreen::MirScreen(
61 std::shared_ptr<usc::ScreenHardware> const& screen_hardware,
62 std::shared_ptr<mir::compositor::Compositor> const& compositor,
63@@ -48,9 +93,9 @@
64 alarm_factory{alarm_factory},
65 clock{clock},
66 power_off_alarm{alarm_factory->create_alarm(
67- std::bind(&usc::MirScreen::power_off_alarm_notification, this))},
68+ std::make_shared<PowerOffLockableCallback>(this))},
69 dimmer_alarm{alarm_factory->create_alarm(
70- std::bind(&usc::MirScreen::dimmer_alarm_notification, this))},
71+ std::make_shared<DimmerLockableCallback>(this))},
72 inactivity_timeouts(inactivity_timeouts),
73 notification_timeouts(notification_timeouts),
74 current_power_mode{MirPowerMode::mir_power_mode_on},
75@@ -313,14 +358,12 @@
76
77 void usc::MirScreen::power_off_alarm_notification()
78 {
79- std::lock_guard<std::mutex> lock{guard};
80 configure_display_l(MirPowerMode::mir_power_mode_off, PowerStateChangeReason::inactivity);
81 next_power_off = {};
82 }
83
84 void usc::MirScreen::dimmer_alarm_notification()
85 {
86- std::lock_guard<std::mutex> lock{guard};
87 screen_hardware->set_dim_backlight();
88 next_dimming = {};
89 }
90
91=== modified file 'src/mir_screen.h'
92--- src/mir_screen.h 2015-07-17 12:43:46 +0000
93+++ src/mir_screen.h 2015-09-23 20:13:49 +0000
94@@ -73,7 +73,16 @@
95 void register_power_state_change_handler(
96 PowerStateChangeHandler const& power_state_change_handler) override;
97
98+protected:
99+ // These are protected virtual because we need to override them in tests
100+ virtual void power_off_alarm_notification();
101+ virtual void dimmer_alarm_notification();
102+
103 private:
104+ class LockableCallback;
105+ class PowerOffLockableCallback;
106+ class DimmerLockableCallback;
107+
108 void set_screen_power_mode_l(MirPowerMode mode, PowerStateChangeReason reason);
109 void configure_display_l(MirPowerMode mode, PowerStateChangeReason reason);
110
111@@ -84,8 +93,6 @@
112 Timeouts timeouts_for(PowerStateChangeReason reason);
113 bool is_screen_change_allowed(MirPowerMode mode, PowerStateChangeReason reason);
114
115- void power_off_alarm_notification();
116- void dimmer_alarm_notification();
117 void long_press_alarm_notification();
118
119 std::shared_ptr<usc::ScreenHardware> const screen_hardware;
120
121=== modified file 'tests/integration-tests/CMakeLists.txt'
122--- tests/integration-tests/CMakeLists.txt 2015-08-26 08:00:27 +0000
123+++ tests/integration-tests/CMakeLists.txt 2015-09-23 20:13:49 +0000
124@@ -38,6 +38,7 @@
125 test_unity_input_service.cpp
126 test_external_spinner.cpp
127 test_powerd_mediator.cpp
128+ test_deadlock_lp1491566.cpp
129 )
130
131 target_link_libraries(
132
133=== added file 'tests/integration-tests/test_deadlock_lp1491566.cpp'
134--- tests/integration-tests/test_deadlock_lp1491566.cpp 1970-01-01 00:00:00 +0000
135+++ tests/integration-tests/test_deadlock_lp1491566.cpp 2015-09-23 20:13:49 +0000
136@@ -0,0 +1,283 @@
137+/*
138+ * Copyright © 2015 Canonical Ltd.
139+ *
140+ * This program is free software: you can redistribute it and/or modify
141+ * it under the terms of the GNU General Public License version 3 as
142+ * published by the Free Software Foundation.
143+ *
144+ * This program is distributed in the hope that it will be useful,
145+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
146+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
147+ * GNU General Public License for more details.
148+ *
149+ * You should have received a copy of the GNU General Public License
150+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
151+ *
152+ * Authored by: Alexandros Frantzis <alexandros.frantzis@canonical.com>
153+ */
154+
155+#include "src/server.h"
156+#include "src/mir_screen.h"
157+#include "src/screen_hardware.h"
158+#include "src/power_state_change_reason.h"
159+#include "spin_wait.h"
160+
161+#include <mir/compositor/compositor.h>
162+#include <mir/main_loop.h>
163+#include <mir/graphics/display.h>
164+#include <mir/graphics/display_configuration.h>
165+#include <mir/graphics/gl_context.h>
166+#include <mir/input/touch_visualizer.h>
167+
168+#include <gtest/gtest.h>
169+#include <gmock/gmock.h>
170+
171+#include <thread>
172+#include <future>
173+
174+namespace mg = mir::graphics;
175+
176+namespace
177+{
178+
179+struct NullCompositor : mir::compositor::Compositor
180+{
181+ void start() override {}
182+ void stop() override {}
183+};
184+
185+struct StubDisplayConfiguration : mg::DisplayConfiguration
186+{
187+ StubDisplayConfiguration()
188+ {
189+ conf_output.power_mode = MirPowerMode::mir_power_mode_on;
190+ }
191+
192+ void for_each_card(std::function<void(mg::DisplayConfigurationCard const&)> f) const override
193+ {
194+ }
195+
196+ void for_each_output(std::function<void(mg::DisplayConfigurationOutput const&)> f) const override
197+ {
198+ f(conf_output);
199+ }
200+
201+ void for_each_output(std::function<void(mg::UserDisplayConfigurationOutput&)> f)
202+ {
203+ mg::UserDisplayConfigurationOutput user{conf_output};
204+ f(user);
205+ }
206+
207+ mg::DisplayConfigurationOutput conf_output;
208+};
209+
210+struct StubDisplay : mg::Display
211+{
212+ void for_each_display_sync_group(std::function<void(mg::DisplaySyncGroup&)> const& f) override
213+ {
214+ }
215+
216+ std::unique_ptr<mg::DisplayConfiguration> configuration() const override
217+ {
218+ return std::unique_ptr<mg::DisplayConfiguration>{new StubDisplayConfiguration{}};
219+ }
220+
221+ void configure(mg::DisplayConfiguration const&) override {}
222+
223+ void register_configuration_change_handler(
224+ mg::EventHandlerRegister& ,
225+ mg::DisplayConfigurationChangeHandler const& ) override
226+ {
227+ }
228+
229+ void register_pause_resume_handlers(
230+ mg::EventHandlerRegister&,
231+ mg::DisplayPauseHandler const&,
232+ mg::DisplayResumeHandler const&) override
233+ {
234+ }
235+
236+ void pause() override {}
237+ void resume() override {}
238+
239+ std::shared_ptr<mg::Cursor> create_hardware_cursor(
240+ std::shared_ptr<mg::CursorImage> const&) override
241+ {
242+ return{};
243+ }
244+
245+ std::unique_ptr<mg::GLContext> create_gl_context() override
246+ {
247+ return std::unique_ptr<mg::GLContext>{};
248+ }
249+};
250+
251+struct StubScreenHardware : usc::ScreenHardware
252+{
253+ void set_dim_backlight() override {}
254+ void set_normal_backlight() override {}
255+ void turn_off_backlight() override {}
256+
257+ void change_backlight_values(int dim_brightness, int normal_brightness) override {}
258+
259+ void allow_suspend() override {}
260+ void disable_suspend() override {}
261+
262+ void enable_auto_brightness(bool) override {}
263+ bool auto_brightness_supported() override { return true; }
264+ void set_brightness(int) override {}
265+ int min_brightness() override { return 100; }
266+ int max_brightness() override { return 0; }
267+
268+ void enable_proximity(bool) override { }
269+};
270+
271+struct NullTouchVisualizer : mir::input::TouchVisualizer
272+{
273+ void enable() override {}
274+ void disable() override {}
275+ void visualize_touches(std::vector<Spot> const& touches) override {}
276+};
277+
278+class TestMirScreen : public usc::MirScreen
279+{
280+public:
281+ using usc::MirScreen::MirScreen;
282+
283+ void dimmer_alarm_notification() override
284+ {
285+ before_dimmer_alarm_func();
286+ usc::MirScreen::dimmer_alarm_notification();
287+ }
288+
289+ void power_off_alarm_notification() override
290+ {
291+ before_power_off_alarm_func();
292+ usc::MirScreen::power_off_alarm_notification();
293+ }
294+
295+ std::function<void()> before_dimmer_alarm_func = []{};
296+ std::function<void()> before_power_off_alarm_func = []{};
297+};
298+
299+struct DeadlockLP1491566 : public testing::Test
300+{
301+ DeadlockLP1491566()
302+ {
303+ main_loop_thread = std::thread{[this] { main_loop->run(); }};
304+ }
305+
306+ ~DeadlockLP1491566()
307+ {
308+ main_loop->stop();
309+ main_loop_thread.join();
310+ }
311+
312+ std::future<void> async_set_screen_power_mode()
313+ {
314+ return std::async(
315+ std::launch::async,
316+ [this]
317+ {
318+ mir_screen.set_screen_power_mode(
319+ MirPowerMode::mir_power_mode_on,
320+ PowerStateChangeReason::power_key);
321+ });
322+ }
323+
324+ void schedule_inactivity_handlers()
325+ {
326+ // We set the screen power mode to on, which will
327+ // schedule inactivity handlers
328+ mir_screen.set_screen_power_mode(
329+ MirPowerMode::mir_power_mode_on,
330+ PowerStateChangeReason::power_key);
331+ }
332+
333+ void wait_for_async_operation(std::future<void>& future)
334+ {
335+ if (!usc::test::spin_wait_for_condition_or_timeout(
336+ [&future] { return future.valid(); },
337+ std::chrono::seconds{3}))
338+ {
339+ throw std::runtime_error{"Future is not valid!"};
340+ }
341+
342+ if (future.wait_for(std::chrono::seconds{3}) != std::future_status::ready)
343+ {
344+ std::cerr << "Deadlock detected. Aborting." << std::endl;
345+ abort();
346+ }
347+ }
348+
349+ char *argv[1] = {nullptr};
350+ usc::Server server{0, argv};
351+ std::shared_ptr<mir::MainLoop> const main_loop{server.the_main_loop()};
352+ std::chrono::milliseconds const power_off_timeout{200};
353+ std::chrono::milliseconds const dimmer_timeout{100};
354+
355+ TestMirScreen mir_screen{
356+ std::make_shared<StubScreenHardware>(),
357+ std::make_shared<NullCompositor>(),
358+ std::make_shared<StubDisplay>(),
359+ std::make_shared<NullTouchVisualizer>(),
360+ main_loop,
361+ server.the_clock(),
362+ {power_off_timeout, dimmer_timeout},
363+ {power_off_timeout, dimmer_timeout}};
364+
365+ std::thread main_loop_thread;
366+};
367+
368+}
369+
370+// The following tests reproduce the situation we are seeing in bug 1491566,
371+// where a set_screen_power_mode request tries to run concurrently with either
372+// the dimmer or power-off timeout handlers. The deadlock is caused by two
373+// threads trying to acquire the internal alarm lock and the MirScreen lock in
374+// opposite orders.
375+//
376+// To reproduce the race in these tests, we need to run the
377+// set_screen_power_mode request just before the timeout handlers are invoked,
378+// while the thread invoking the timeout handlers holds the internal alarm
379+// lock, but before it has acquired the MirScreen lock. The thread calling
380+// set_screen_power_mode will then first acquire the MirScreen lock and then
381+// try to acquire the internal alarm clock and block. The timeout handler
382+// thread will eventually resume and try to get the MirScreen lock, leading to
383+// a deadlock.
384+
385+TEST_F(DeadlockLP1491566, between_dimmer_handler_and_screen_power_mode_request_is_averted)
386+{
387+ std::future<void> future;
388+
389+ mir_screen.before_dimmer_alarm_func =
390+ [this, &future]
391+ {
392+ future = async_set_screen_power_mode();
393+ // Wait a bit for async operation to get processed
394+ std::this_thread::sleep_for(
395+ std::chrono::milliseconds{500});
396+ };
397+
398+ schedule_inactivity_handlers();
399+
400+ wait_for_async_operation(future);
401+}
402+
403+TEST_F(DeadlockLP1491566, between_power_off_handler_and_screen_power_mode_request_is_averted)
404+{
405+ std::future<void> future;
406+
407+ mir_screen.before_power_off_alarm_func =
408+ [this, &future]
409+ {
410+ future = async_set_screen_power_mode();
411+ // Wait a bit for async operation to get processed
412+ std::this_thread::sleep_for(
413+ std::chrono::milliseconds{500});
414+ };
415+
416+ schedule_inactivity_handlers();
417+
418+ wait_for_async_operation(future);
419+}
420
421=== modified file 'tests/unit-tests/advanceable_timer.cpp'
422--- tests/unit-tests/advanceable_timer.cpp 2015-07-15 13:48:30 +0000
423+++ tests/unit-tests/advanceable_timer.cpp 2015-09-23 20:13:49 +0000
424@@ -17,6 +17,7 @@
425 */
426
427 #include "advanceable_timer.h"
428+#include <mir/lockable_callback.h>
429 #include <algorithm>
430
431 namespace
432@@ -59,7 +60,7 @@
433 {
434 AdvanceableAlarm(
435 mir::time::Timestamp now,
436- std::function<void(void)> const& callback)
437+ std::shared_ptr<mir::LockableCallback> const& callback)
438 : now{now}, callback{callback}
439 {
440 }
441@@ -101,7 +102,9 @@
442 {
443 state_ = mir::time::Alarm::triggered;
444 lock.unlock();
445- callback();
446+ callback->lock();
447+ (*callback)();
448+ callback->unlock();
449 }
450 }
451
452@@ -113,7 +116,7 @@
453
454 private:
455 mir::time::Timestamp now;
456- std::function<void()> const callback;
457+ std::shared_ptr<mir::LockableCallback> const callback;
458 mir::time::Timestamp next_trigger_;
459 mutable std::mutex mutex;
460 mir::time::Alarm::State state_ = mir::time::Alarm::triggered;
461@@ -123,6 +126,26 @@
462 std::unique_ptr<mir::time::Alarm> AdvanceableTimer::create_alarm(
463 std::function<void()> const& callback)
464 {
465+ struct SimpleLockableCallback : public mir::LockableCallback
466+ {
467+ SimpleLockableCallback(std::function<void()> const& callback)
468+ : callback{callback}
469+ {
470+ }
471+
472+ void operator()() override { callback(); }
473+ void lock() override {}
474+ void unlock() override {}
475+
476+ std::function<void()> const callback;
477+ };
478+
479+ return create_alarm(std::make_shared<SimpleLockableCallback>(callback));
480+}
481+
482+std::unique_ptr<mir::time::Alarm> AdvanceableTimer::create_alarm(
483+ std::shared_ptr<mir::LockableCallback> const& callback)
484+{
485 auto const adv_alarm =
486 std::make_shared<detail::AdvanceableAlarm>(now(), callback);
487
488@@ -131,12 +154,6 @@
489 return std::unique_ptr<AlarmWrapper>(new AlarmWrapper{adv_alarm});
490 }
491
492-std::unique_ptr<mir::time::Alarm> AdvanceableTimer::create_alarm(
493- std::shared_ptr<mir::LockableCallback> const&)
494-{
495- throw std::runtime_error("Not implemented");
496-}
497-
498 void AdvanceableTimer::advance_by(std::chrono::milliseconds advance)
499 {
500 {

Subscribers

People subscribed via source and target branches