Mir

Merge lp:~albaguirre/mir/fix-1522105 into lp:mir

Proposed by Alberto Aguirre
Status: Rejected
Rejected by: Alberto Aguirre
Proposed branch: lp:~albaguirre/mir/fix-1522105
Merge into: lp:mir
Diff against target: 253 lines (+87/-24)
3 files modified
src/server/scene/surface_stack.cpp (+15/-9)
src/server/scene/surface_stack.h (+5/-4)
tests/unit-tests/scene/test_surface_stack.cpp (+67/-11)
To merge this branch: bzr merge lp:~albaguirre/mir/fix-1522105
Reviewer Review Type Date Requested Status
Alan Griffiths Abstain
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+279393@code.launchpad.net

Commit message

Fix SurfaceStack::add_observer iterating over surface list while dropping/reacquiring its mutex. (LP: #1522105)

Another thread could call SurfaceStack::surface_removed while the lock is dropped, invalidating the iterator, leading to runtime errors.

A secondary lock is used to ensure surface_removed and add_observer are mutually exclusive thus ensuring the order of calls to Observer::surface_exists and Observer::surface_removed is as expected and so that Observer::surface_exists is never called with a stale surface pointer.

Description of the change

A bit ugly but fixes the bug. Note that a surface_exists handler which triggers another thread to remove a surface from the scene could potentially deadlock - but I fail to a reason we should support that.

To post a comment you must log in.
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Clearly the "before" is worse than the proposal - it holds (and uses) a reference to an element that needs to be locked to avoid invalidation.

While there are no obvious problems with the proposed approach it isn't obvious that there are no problems. Maybe this is a case for RecursiveReadWriteMutex?

review: Needs Information
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

I've put up an alternative using RecursiveReadWriteMutex:

    lp:~alan-griffiths/mir/fix-1522105/+merge/279418

I'll let the voters choose.

review: Abstain

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/server/scene/surface_stack.cpp'
2--- src/server/scene/surface_stack.cpp 2015-11-05 11:51:04 +0000
3+++ src/server/scene/surface_stack.cpp 2015-12-03 07:23:10 +0000
4@@ -221,7 +221,7 @@
5 }
6 overlays.erase(p);
7 }
8-
9+
10 emit_scene_changed();
11 }
12
13@@ -252,7 +252,7 @@
14 void ms::SurfaceStack::remove_surface(std::weak_ptr<Surface> const& surface)
15 {
16 auto const keep_alive = surface.lock();
17-
18+ std::lock_guard<decltype(remove_guard)> lg{remove_guard};
19 bool found_surface = false;
20 {
21 std::lock_guard<decltype(guard)> lg(guard);
22@@ -378,13 +378,19 @@
23 {
24 observers.add(observer);
25
26- // Notify observer of existing surfaces
27+ // NOTE: Guarantee that surface_exists will not be called with stale surfaces
28+ // by preventing surface_removed from removing surfaces before we are done here
29+ std::lock_guard<decltype(remove_guard)> rlk{remove_guard};
30+
31+ // A copy of the surfaces is taken, as raise and add_surface can still invalidate
32+ // the iterators and surface_exists should not be called with the surface list lock held.
33 std::unique_lock<decltype(guard)> lk(guard);
34- for (auto &surface : surfaces)
35+ auto const surfaces_copy = surfaces;
36+ lk.unlock();
37+
38+ for (auto &surface : surfaces_copy)
39 {
40- lk.unlock();
41 observer->surface_exists(surface.get());
42- lk.lock();
43 }
44 }
45
46@@ -393,13 +399,13 @@
47 auto o = observer.lock();
48 if (!o)
49 BOOST_THROW_EXCEPTION(std::logic_error("Invalid observer (destroyed)"));
50-
51+
52 o->end_observation();
53-
54+
55 observers.remove(o);
56 }
57
58-void ms::Observers::surface_added(ms::Surface* surface)
59+void ms::Observers::surface_added(ms::Surface* surface)
60 {
61 for_each([&](std::shared_ptr<Observer> const& observer)
62 { observer->surface_added(surface); });
63
64=== modified file 'src/server/scene/surface_stack.h'
65--- src/server/scene/surface_stack.h 2015-11-25 16:32:07 +0000
66+++ src/server/scene/surface_stack.h 2015-12-03 07:23:10 +0000
67@@ -88,16 +88,16 @@
68 void add_surface(
69 std::shared_ptr<Surface> const& surface,
70 input::InputReceptionMode input_mode) override;
71-
72+
73 auto surface_at(geometry::Point) const -> std::shared_ptr<Surface> override;
74
75 void add_observer(std::shared_ptr<Observer> const& observer) override;
76 void remove_observer(std::weak_ptr<Observer> const& observer) override;
77-
78+
79 // Intended for input overlays, as described in mir::input::Scene documentation.
80 void add_input_visualization(std::shared_ptr<graphics::Renderable> const& overlay) override;
81 void remove_input_visualization(std::weak_ptr<graphics::Renderable> const& overlay) override;
82-
83+
84 void emit_scene_changed() override;
85
86 private:
87@@ -107,6 +107,7 @@
88 void update_rendering_tracker_compositors();
89
90 std::mutex mutable guard;
91+ std::recursive_mutex mutable remove_guard;
92
93 std::shared_ptr<InputRegistrar> const input_registrar;
94 std::shared_ptr<SceneReport> const report;
95@@ -114,7 +115,7 @@
96 std::vector<std::shared_ptr<Surface>> surfaces;
97 std::map<Surface*,std::shared_ptr<RenderingTracker>> rendering_trackers;
98 std::set<compositor::CompositorID> registered_compositors;
99-
100+
101 std::vector<std::shared_ptr<graphics::Renderable>> overlays;
102
103 Observers observers;
104
105=== modified file 'tests/unit-tests/scene/test_surface_stack.cpp'
106--- tests/unit-tests/scene/test_surface_stack.cpp 2015-11-05 11:51:04 +0000
107+++ tests/unit-tests/scene/test_surface_stack.cpp 2015-12-03 07:23:10 +0000
108@@ -349,7 +349,7 @@
109 .WillByDefault(InvokeWithoutArgs([&]{ready++;}));
110 ON_CALL(*mock_queue, compositor_acquire(_))
111 .WillByDefault(InvokeWithoutArgs([&]{ready--; return mt::fake_shared(stub_buffer); }));
112-
113+
114 auto surface = std::make_shared<ms::BasicSurface>(
115 std::string("stub"),
116 geom::Rectangle{{},{}},
117@@ -620,20 +620,76 @@
118 {
119 using namespace ::testing;
120
121- using namespace ::testing;
122-
123 MockSceneObserver observer;
124
125 InSequence seq;
126 EXPECT_CALL(observer, surface_exists(stub_surface1.get())).Times(1);
127 EXPECT_CALL(observer, surface_exists(stub_surface2.get())).Times(1);
128-
129+
130 stack.add_surface(stub_surface1, default_params.input_mode);
131 stack.add_surface(stub_surface2, default_params.input_mode);
132
133 stack.add_observer(mt::fake_shared(observer));
134 }
135
136+TEST_F(SurfaceStack, scene_observer_can_query_scene_within_surface_exists_notification)
137+{
138+ using namespace ::testing;
139+
140+ MockSceneObserver observer;
141+
142+ auto const scene_query = [&]{
143+ stack.for_each([&](std::shared_ptr<mi::Surface> const& surface){
144+ EXPECT_THAT(surface.get(), Eq(stub_surface1.get()));
145+ });
146+ };
147+ EXPECT_CALL(observer, surface_exists(stub_surface1.get())).Times(1)
148+ .WillOnce(InvokeWithoutArgs(scene_query));
149+
150+ stack.add_surface(stub_surface1, default_params.input_mode);
151+ stack.add_observer(mt::fake_shared(observer));
152+}
153+
154+TEST_F(SurfaceStack, scene_observer_can_async_query_scene_within_surface_exists_notification)
155+{
156+ using namespace ::testing;
157+
158+ MockSceneObserver observer;
159+
160+ auto const scene_query = [&]{
161+ stack.for_each([&](std::shared_ptr<mi::Surface> const& surface){
162+ EXPECT_THAT(surface.get(), Eq(stub_surface1.get()));
163+ });
164+ };
165+
166+ auto const async_scene_query = [&]{
167+ std::async(std::launch::async, scene_query);
168+ };
169+
170+ EXPECT_CALL(observer, surface_exists(stub_surface1.get())).Times(1)
171+ .WillOnce(InvokeWithoutArgs(async_scene_query));
172+
173+ stack.add_surface(stub_surface1, default_params.input_mode);
174+ stack.add_observer(mt::fake_shared(observer));
175+}
176+
177+
178+TEST_F(SurfaceStack, scene_observer_can_remove_surface_from_scene_within_surface_exists_notification)
179+{
180+ using namespace ::testing;
181+
182+ MockSceneObserver observer;
183+
184+ auto const surface_removal = [&]{
185+ stack.remove_surface(stub_surface1);
186+ };
187+ EXPECT_CALL(observer, surface_exists(stub_surface1.get())).Times(1)
188+ .WillOnce(InvokeWithoutArgs(surface_removal));
189+
190+ stack.add_surface(stub_surface1, default_params.input_mode);
191+ stack.add_observer(mt::fake_shared(observer));
192+}
193+
194 TEST_F(SurfaceStack, surfaces_reordered)
195 {
196 using namespace ::testing;
197@@ -923,7 +979,7 @@
198 TEST_F(SurfaceStack, overlays_do_not_appear_in_input_enumeration)
199 {
200 mtd::StubRenderable r;
201-
202+
203 stack.add_surface(stub_surface1, default_params.input_mode);
204 stack.add_surface(stub_surface2, default_params.input_mode);
205
206@@ -946,7 +1002,7 @@
207 using namespace ::testing;
208
209 mtd::StubRenderable r;
210-
211+
212 stack.add_surface(stub_surface1, default_params.input_mode);
213 stack.add_input_visualization(mt::fake_shared(r));
214 stack.add_surface(stub_surface2, default_params.input_mode);
215@@ -956,7 +1012,7 @@
216 ElementsAre(
217 SceneElementForSurface(stub_surface1),
218 SceneElementForSurface(stub_surface2),
219- SceneElementForStream(mt::fake_shared(r))));
220+ SceneElementForStream(mt::fake_shared(r))));
221 }
222
223 TEST_F(SurfaceStack, removed_overlays_are_removed)
224@@ -964,7 +1020,7 @@
225 using namespace ::testing;
226
227 mtd::StubRenderable r;
228-
229+
230 stack.add_surface(stub_surface1, default_params.input_mode);
231 stack.add_input_visualization(mt::fake_shared(r));
232 stack.add_surface(stub_surface2, default_params.input_mode);
233@@ -975,7 +1031,7 @@
234 SceneElementForSurface(stub_surface1),
235 SceneElementForSurface(stub_surface2),
236 SceneElementForStream(mt::fake_shared(r))));
237-
238+
239 stack.remove_input_visualization(mt::fake_shared(r));
240
241 EXPECT_THAT(
242@@ -991,10 +1047,10 @@
243
244 EXPECT_CALL(o1, scene_changed()).Times(1);
245 EXPECT_CALL(o2, scene_changed()).Times(1);
246-
247+
248 stack.add_observer(mt::fake_shared(o1));
249 stack.add_observer(mt::fake_shared(o2));
250-
251+
252 stack.emit_scene_changed();
253 }
254

Subscribers

People subscribed via source and target branches