Mir

Merge lp:~alan-griffiths/mir/fix-1376324 into lp:mir

Proposed by Alan Griffiths
Status: Merged
Approved by: Alberto Aguirre
Approved revision: no longer in the source branch.
Merged at revision: 1965
Proposed branch: lp:~alan-griffiths/mir/fix-1376324
Merge into: lp:mir
Diff against target: 278 lines (+148/-0)
13 files modified
src/include/server/mir/compositor/buffer_stream.h (+1/-0)
src/server/compositor/buffer_bundle.h (+1/-0)
src/server/compositor/buffer_queue.cpp (+6/-0)
src/server/compositor/buffer_queue.h (+1/-0)
src/server/compositor/buffer_stream_surfaces.cpp (+5/-0)
src/server/compositor/buffer_stream_surfaces.h (+1/-0)
src/server/scene/basic_surface.cpp (+3/-0)
tests/include/mir_test_doubles/mock_buffer_bundle.h (+1/-0)
tests/include/mir_test_doubles/mock_buffer_stream.h (+1/-0)
tests/include/mir_test_doubles/stub_buffer_stream.h (+1/-0)
tests/integration-tests/CMakeLists.txt (+1/-0)
tests/integration-tests/surface_composition.cpp (+125/-0)
tests/integration-tests/test_exchange_buffer.cpp (+1/-0)
To merge this branch: bzr merge lp:~alan-griffiths/mir/fix-1376324
Reviewer Review Type Date Requested Status
Andreas Pokorny (community) Approve
Daniel van Vugt Needs Information
Alexandros Frantzis (community) Approve
Alberto Aguirre (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+236862@code.launchpad.net

Commit message

scene, compositor: ensure a buffer requested by a surface is not delivered after the surface is deleted.

Description of the change

scene, compositor: ensure a buffer requested by a surface is not delivered after the surface is deleted.

I can find nothing in the Mir code that prevents a surface being rendered while the same surface is being deleted. If a pending buffer request from the client is honored after the deletion then we would have a stack trace like lp:1376324

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alberto Aguirre (albaguirre) wrote :

LGTM.

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

Theoretically there shouldn't be any use of deleted surfaces if we're maintaining ownership properly (shared_ptr). Is this a consequence of those raw Buffer pointers we still have in places?

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

Oh I see. The real issue is that the callback object doesn't maintain a reference to the object (Surface) that owns the callback.

And it appears the only place where such an offending callback exists (twice) is in session_mediator.cpp; search for "swap_buffers". But even those callbacks that SessionMediator is passing to swap_buffers appear to be safe. We must be failing to check surface_id is valid then?...

I think there might be a race between a callback that's in-flight and the surface destruction. I can't see any synchronization that would prevent the crash from still happening if they both occur at basically the same time.

Why not just make the callbacks themselves uncrashable (since the SessionMediator has a longer lifetime it can check things)? That would also not be vulnerable to racing like this solution seems to be.

Also, where you have the fix right now in ~BasicSurface(), is the same place where the BufferStream will get destroyed, meaning the BufferQueue will be destroyed and no callbacks can happen after ~BasicSurface() right now anyway. So I'm not convinced this proposal is making anything better yet. Dead surfaces theoretically can never receive callbacks already, because a callback can't happen if the BufferQueue doesn't exist any more.

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

Daniel,

1. The pending callback is owned by BufferQueue which is (indirectly) owned by BasicSurface. If the callback were to own the SocketConnection (which indirectly owns the BasicSurface) then there would be an ownership loop.

2. If the BufferQueue were deleted (as you are assuming) when the BasicSurface is destroyed then there wouldn't be a problem. (And the new test would pass in -r 1955.)

HTH

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

If I understand the issue correctly: a Surface hands out Temporary*Buffers that use the BufferQueue (and keep a strong pointer to it). When a surface is deleted, the compositor may be holding a Temporary*Buffer and therefore the BufferQueue is kept alive. During composition, as buffers are released, pending client notifications may be invoked with invalid objects. This is particularly likely to occur with overlays, since for overlays we keep buffer references for longer (until we get the next frame).

In the error report, the crash happens during message serialization, which indicates that ProtobufResponder may have been deleted. So this likely occurs when an application has shut down or is shutting down.

The fix seems plausible, and lacking any way to reproduce the original problem, we have to wait and see if it eliminates the error reports. Perhaps there are other options for fixing this, but let's first ascertain the cause and we can move from there.

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

Aha! Yes thanks Alexandros for pointing that out. The BufferQueue can indeed live longer than the Surface that owns it, because the compositor gets its own temporary reference to the BufferStream (hence BufferQueue) here:

class SurfaceSnapshot : public mg::Renderable
{
public:
    SurfaceSnapshot(
        std::shared_ptr<mc::BufferStream> const& stream,
        void const* compositor_id,
        geom::Rectangle const& position,
        glm::mat4 const& transform,
        bool visible,
        float alpha,
        bool shaped,
        mg::Renderable::ID id)
    : underlying_buffer_stream{stream},

This is a relatively new feature. If SurfaceSnapshot didn't exist then we wouldn't have a bug. Because then the BufferStream would be definitely destroyed with the BasicSurface.

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

Hmm, actually SurfaceSnapshot doesn't really need the BufferStream at all. The SurfaceSnapshot is created at the instance when we want to composite. We should instead just pass it a buffer, and not let it hold a reference to the BufferStream. In theory that should guarantee that BasicSurface holds a unique pointer (which we should enforce as unique_ptr) to BufferStream and thus it would become impossible for the BufferQueue to live longer than the surface it calls back into.

I think that would be a nicer (and smaller?) fix, if it works.

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

> I think that would be a nicer (and smaller?) fix, if it works.

Just changing SurfaceSnapshot alone wouldn't help. We would still have the problem because we would have to keep references to Buffer objects, which are really instances of Temporary*Buffer and thus keep a strong reference to BufferQueue.

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

I accept that injecting "surface_buffer_stream->drop_client_requests()" in the destructor seems inelegant.

But the alternative of disentangling the ownership of the buffer from the lifetime of the BufferStream is far from trivial and may not be any clearer.

This is a pragmatic solution and allows us to determine if the crash reports are affected.

Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :

ok

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/include/server/mir/compositor/buffer_stream.h'
2--- src/include/server/mir/compositor/buffer_stream.h 2014-09-11 05:51:44 +0000
3+++ src/include/server/mir/compositor/buffer_stream.h 2014-10-02 13:13:30 +0000
4@@ -54,6 +54,7 @@
5 virtual void force_requests_to_complete() = 0;
6 virtual int buffers_ready_for_compositor() const = 0;
7 virtual void drop_old_buffers() = 0;
8+ virtual void drop_client_requests() = 0;
9 };
10
11 }
12
13=== modified file 'src/server/compositor/buffer_bundle.h'
14--- src/server/compositor/buffer_bundle.h 2014-09-11 05:51:44 +0000
15+++ src/server/compositor/buffer_bundle.h 2014-10-02 13:13:30 +0000
16@@ -68,6 +68,7 @@
17 */
18 virtual int buffers_free_for_client() const = 0;
19 virtual void drop_old_buffers() = 0;
20+ virtual void drop_client_requests() = 0;
21
22 protected:
23 BufferBundle() = default;
24
25=== modified file 'src/server/compositor/buffer_queue.cpp'
26--- src/server/compositor/buffer_queue.cpp 2014-09-30 06:11:33 +0000
27+++ src/server/compositor/buffer_queue.cpp 2014-10-02 13:13:30 +0000
28@@ -502,3 +502,9 @@
29 release(buffer, std::move(lock));
30 }
31 }
32+
33+void mc::BufferQueue::drop_client_requests()
34+{
35+ std::unique_lock<std::mutex> lock(guard);
36+ pending_client_notifications.clear();
37+}
38
39=== modified file 'src/server/compositor/buffer_queue.h'
40--- src/server/compositor/buffer_queue.h 2014-09-30 06:11:33 +0000
41+++ src/server/compositor/buffer_queue.h 2014-10-02 13:13:30 +0000
42@@ -63,6 +63,7 @@
43 bool framedropping_allowed() const;
44 bool is_a_current_buffer_user(void const* user_id) const;
45 void drop_old_buffers() override;
46+ void drop_client_requests() override;
47
48 private:
49 void give_buffer_to_client(graphics::Buffer* buffer,
50
51=== modified file 'src/server/compositor/buffer_stream_surfaces.cpp'
52--- src/server/compositor/buffer_stream_surfaces.cpp 2014-09-11 05:51:44 +0000
53+++ src/server/compositor/buffer_stream_surfaces.cpp 2014-10-02 13:13:30 +0000
54@@ -94,3 +94,8 @@
55 {
56 buffer_bundle->drop_old_buffers();
57 }
58+
59+void mc::BufferStreamSurfaces::drop_client_requests()
60+{
61+ buffer_bundle->drop_client_requests();
62+}
63
64=== modified file 'src/server/compositor/buffer_stream_surfaces.h'
65--- src/server/compositor/buffer_stream_surfaces.h 2014-09-11 05:51:44 +0000
66+++ src/server/compositor/buffer_stream_surfaces.h 2014-10-02 13:13:30 +0000
67@@ -53,6 +53,7 @@
68 void force_requests_to_complete() override;
69 int buffers_ready_for_compositor() const override;
70 void drop_old_buffers() override;
71+ void drop_client_requests() override;
72
73 protected:
74 BufferStreamSurfaces(const BufferStreamSurfaces&) = delete;
75
76=== modified file 'src/server/scene/basic_surface.cpp'
77--- src/server/scene/basic_surface.cpp 2014-09-26 08:16:25 +0000
78+++ src/server/scene/basic_surface.cpp 2014-10-02 13:13:30 +0000
79@@ -151,6 +151,9 @@
80 ms::BasicSurface::~BasicSurface() noexcept
81 {
82 report->surface_deleted(this, surface_name);
83+
84+ if (surface_buffer_stream) // some tests use null for surface_buffer_stream
85+ surface_buffer_stream->drop_client_requests();
86 }
87
88 std::shared_ptr<mc::BufferStream> ms::BasicSurface::buffer_stream() const
89
90=== modified file 'tests/include/mir_test_doubles/mock_buffer_bundle.h'
91--- tests/include/mir_test_doubles/mock_buffer_bundle.h 2014-09-11 05:51:44 +0000
92+++ tests/include/mir_test_doubles/mock_buffer_bundle.h 2014-10-02 13:13:30 +0000
93@@ -51,6 +51,7 @@
94 MOCK_METHOD0(drop_old_buffers, void());
95 int buffers_ready_for_compositor() const override { return 1; }
96 int buffers_free_for_client() const override { return 1; }
97+ void drop_client_requests() override {}
98 };
99
100 }
101
102=== modified file 'tests/include/mir_test_doubles/mock_buffer_stream.h'
103--- tests/include/mir_test_doubles/mock_buffer_stream.h 2014-09-11 05:51:44 +0000
104+++ tests/include/mir_test_doubles/mock_buffer_stream.h 2014-10-02 13:13:30 +0000
105@@ -58,6 +58,7 @@
106 MOCK_METHOD0(force_requests_to_complete, void());
107 MOCK_CONST_METHOD0(buffers_ready_for_compositor, int());
108 MOCK_METHOD0(drop_old_buffers, void());
109+ MOCK_METHOD0(drop_client_requests, void());
110 };
111 }
112 }
113
114=== modified file 'tests/include/mir_test_doubles/stub_buffer_stream.h'
115--- tests/include/mir_test_doubles/stub_buffer_stream.h 2014-09-11 05:51:44 +0000
116+++ tests/include/mir_test_doubles/stub_buffer_stream.h 2014-10-02 13:13:30 +0000
117@@ -82,6 +82,7 @@
118 int buffers_ready_for_compositor() const override { return 1; }
119
120 void drop_old_buffers() override {}
121+ void drop_client_requests() override {}
122
123 StubBuffer stub_client_buffer;
124 std::shared_ptr<graphics::Buffer> stub_compositor_buffer;
125
126=== modified file 'tests/integration-tests/CMakeLists.txt'
127--- tests/integration-tests/CMakeLists.txt 2014-10-02 03:05:27 +0000
128+++ tests/integration-tests/CMakeLists.txt 2014-10-02 13:13:30 +0000
129@@ -24,6 +24,7 @@
130 test_session_manager.cpp
131 test_session.cpp
132 session_management.cpp
133+ surface_composition.cpp
134 )
135
136 add_subdirectory(client/)
137
138=== added file 'tests/integration-tests/surface_composition.cpp'
139--- tests/integration-tests/surface_composition.cpp 1970-01-01 00:00:00 +0000
140+++ tests/integration-tests/surface_composition.cpp 2014-10-02 13:13:30 +0000
141@@ -0,0 +1,125 @@
142+/*
143+ * Copyright © 2014 Canonical Ltd.
144+ *
145+ * This program is free software: you can redistribute it and/or modify it
146+ * under the terms of the GNU General Public License version 3,
147+ * as published by the Free Software Foundation.
148+ *
149+ * This program is distributed in the hope that it will be useful,
150+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
151+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
152+ * GNU General Public License for more details.
153+ *
154+ * You should have received a copy of the GNU General Public License
155+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
156+ *
157+ * Authored By: Alan Griffiths <alan@octopull.co.uk>
158+ */
159+
160+#include "src/server/scene/basic_surface.h"
161+#include "src/server/report/null_report_factory.h"
162+#include "src/server/compositor/buffer_stream_surfaces.h"
163+#include "src/server/compositor/buffer_queue.h"
164+
165+#include "mir_test_doubles/null_surface_configurator.h"
166+#include "mir_test_doubles/stub_buffer_allocator.h"
167+#include "mir_test_doubles/stub_frame_dropping_policy_factory.h"
168+#include "mir_test_doubles/stub_input_sender.h"
169+
170+#include <gmock/gmock.h>
171+#include <gtest/gtest.h>
172+
173+namespace geom = mir::geometry;
174+namespace mc = mir::compositor;
175+namespace mg = mir::graphics;
176+namespace mi = mir::input;
177+namespace mr = mir::report;
178+namespace ms = mir::scene;
179+namespace mtd = mir::test::doubles;
180+
181+using namespace testing;
182+
183+namespace
184+{
185+struct SurfaceComposition : Test
186+{
187+ auto create_surface() const
188+ -> std::shared_ptr<ms::Surface>
189+ {
190+ return std::make_shared<ms::BasicSurface>(
191+ std::string("SurfaceComposition"),
192+ geom::Rectangle{{},{}},
193+ false,
194+ create_buffer_stream(),
195+ create_input_channel(),
196+ create_input_sender(),
197+ create_surface_configurator(),
198+ create_cursor_image(),
199+ mr::null_scene_report());
200+
201+ }
202+
203+ int const number_of_buffers = 3;
204+
205+ auto create_buffer_stream() const
206+ ->std::shared_ptr<mc::BufferStream>
207+ {
208+ return std::make_shared<mc::BufferStreamSurfaces>(create_buffer_bundle());
209+ }
210+
211+ auto create_buffer_bundle() const
212+ -> std::shared_ptr<mc::BufferBundle>
213+ {
214+ return std::make_shared<mc::BufferQueue>(
215+ number_of_buffers,
216+ allocator,
217+ basic_properties,
218+ policy_factory);
219+ }
220+
221+ auto create_input_channel() const
222+ -> std::shared_ptr<mi::InputChannel> { return {}; }
223+
224+ auto create_cursor_image() const
225+ -> std::shared_ptr<mg::CursorImage> { return {}; }
226+
227+ auto create_surface_configurator() const
228+ -> std::shared_ptr<ms::SurfaceConfigurator>
229+ { return std::make_shared<mtd::NullSurfaceConfigurator>(); }
230+
231+ auto create_input_sender() const
232+ -> std::shared_ptr<mi::InputSender>
233+ { return std::make_shared<mtd::StubInputSender>(); }
234+
235+ std::shared_ptr<mg::GraphicBufferAllocator> const allocator
236+ {std::make_shared<mtd::StubBufferAllocator>()};
237+
238+ mg::BufferProperties const basic_properties
239+ { geom::Size{3, 4}, mir_pixel_format_abgr_8888, mg::BufferUsage::hardware };
240+
241+ mtd::StubFrameDroppingPolicyFactory policy_factory;
242+};
243+}
244+
245+// Presumptive cause of lp:1376324
246+TEST_F(SurfaceComposition, does_not_send_client_buffers_to_dead_surfaces)
247+{
248+ auto surface = create_surface();
249+
250+ mg::Buffer* old_buffer{nullptr};
251+
252+ auto const callback = [&] (mg::Buffer* new_buffer)
253+ {
254+ // If surface is dead then callback is not expected
255+ EXPECT_THAT(surface.get(), NotNull());
256+ old_buffer = new_buffer;
257+ };
258+
259+ // Exhaust the buffers to ensure we have a pending swap to complete
260+ for (auto i = 0; i != number_of_buffers; ++i)
261+ surface->swap_buffers(old_buffer, callback);
262+
263+ auto const renderable = surface->compositor_snapshot(this);
264+
265+ surface.reset();
266+}
267
268=== modified file 'tests/integration-tests/test_exchange_buffer.cpp'
269--- tests/integration-tests/test_exchange_buffer.cpp 2014-09-15 18:20:14 +0000
270+++ tests/integration-tests/test_exchange_buffer.cpp 2014-10-02 13:13:30 +0000
271@@ -78,6 +78,7 @@
272 void force_requests_to_complete() {};
273 int buffers_ready_for_compositor() const { return -5; }
274 void drop_old_buffers() {}
275+ void drop_client_requests() override {}
276
277 std::vector<std::shared_ptr<mg::Buffer>> client_buffers;
278 std::vector<mg::BufferID> const buffer_id_seq;

Subscribers

People subscribed via source and target branches