Merge lp:~afrantzis/unity-system-compositor/fix-1491566-deadlock into lp:unity-system-compositor
- fix-1491566-deadlock
- Merge into trunk
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 |
Related bugs: |
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_
PS Jenkins bot (ps-jenkins) wrote : | # |
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?
- 254. By Alexandros Frantzis
-
Make MirScreen alarm handlers overridable for test purposes
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 LockableCallbac
Alexandros Frantzis (afrantzis) wrote : | # |
> The LockableCallbac
s/and unlock()//
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:254
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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
Preview Diff
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 | { |
PASSED: Continuous integration, rev:253 jenkins. qa.ubuntu. com/job/ unity-system- compositor- ci/285/ jenkins. qa.ubuntu. com/job/ unity-system- compositor- wily-amd64- ci/73 jenkins. qa.ubuntu. com/job/ unity-system- compositor- wily-armhf- ci/73 jenkins. qa.ubuntu. com/job/ unity-system- compositor- wily-armhf- ci/73/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ unity-system- compositor- wily-i386- ci/73
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- system- compositor- ci/285/ rebuild
http://