Mir

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

Proposed by Alan Griffiths on 2015-08-25
Status: Merged
Merged at revision: 2893
Proposed branch: lp:~alan-griffiths/mir/fix-1486496
Merge into: lp:mir
Prerequisite: lp:~alan-griffiths/mir/workaround-1441553
Diff against target: 296 lines (+152/-7)
8 files modified
src/client/mir_connection.cpp (+2/-2)
src/client/rpc/mir_basic_rpc_channel.cpp (+24/-2)
src/client/rpc/mir_basic_rpc_channel.h (+1/-0)
src/include/common/mir/protobuf/protocol_version.h (+49/-0)
src/server/frontend/socket_connection.cpp (+8/-1)
tests/acceptance-tests/test_client_library.cpp (+65/-0)
tests/integration-tests/test_error_reporting.cpp (+1/-1)
tests/unit-tests/frontend/test_socket_connection.cpp (+2/-1)
To merge this branch: bzr merge lp:~alan-griffiths/mir/fix-1486496
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration 2015-08-25 Needs Fixing on 2015-09-01
Daniel van Vugt Approve on 2015-08-31
Alexandros Frantzis (community) Approve on 2015-08-27
Kevin DuBois (community) Approve on 2015-08-25
Alan Griffiths Pending
Chris Halse Rogers 2015-08-25 Pending
Review via email: mp+269087@code.launchpad.net

Commit Message

client, frontend: Introduce meaningful versioning into the protobuf protocol and check it.
(LP: #1486496)

Description of the Change

client, frontend: Introduce meaningful versioning into the protobuf protocol and check it.

Because the server simply disconnects on a connection failure the client doesn't receive any reason for the failure. However, reporting a connection failure is better than hanging.

To post a comment you must log in.
Kevin DuBois (kdub) wrote :

lgtm

review: Approve
Alexandros Frantzis (afrantzis) wrote :

What if we break the protocol without an API change?

review: Needs Information
Alan Griffiths (alan-griffiths) wrote :

> What if we break the protocol without an API change?

Then the soname doesn't change, clients links to the new libmirclient and everything works.

But I see your point - calling it the protocol version is misleading. Suggestions for a better name please?

Chris Halse Rogers (raof) wrote :

There's actually no guarantee that the client code will link with the new libmirclient. It could be statically linked, or provided by the .snap rather than the system (Snappy core doesn't have mirclient in the base image)

-----Original Message-----
From: "Alan Griffiths" <email address hidden>
Sent: ‎27/‎08/‎2015 18:28
To: "Alexandros Frantzis" <email address hidden>
Subject: Re: [Merge] lp:~alan-griffiths/mir/fix-1486496 into lp:mir

> What if we break the protocol without an API change?

Then the soname doesn't change, clients links to the new libmirclient and everything works.

But I see your point - calling it the protocol version is misleading. Suggestions for a better name please?
--
https://code.launchpad.net/~alan-griffiths/mir/fix-1486496/+merge/269087
You are requested to review the proposed merge of lp:~alan-griffiths/mir/fix-1486496 into lp:mir.

Alan Griffiths (alan-griffiths) wrote :

> There's actually no guarantee that the client code will link with the new
> libmirclient. It could be statically linked, or provided by the .snap rather
> than the system (Snappy core doesn't have mirclient in the base image)

https://bugs.launchpad.net/mir/+bug/1486496/comments/21

Alexandros Frantzis (afrantzis) wrote :

> > What if we break the protocol without an API change?
>
> Then the soname doesn't change, clients links to the new libmirclient and
> everything works.
>
> But I see your point - calling it the protocol version is misleading.
> Suggestions for a better name please?

How about using protocol(epoch, major, minor), where epoch is a value we can increase manually as needed:

current client API -> supported protocol versions

1:3.0 -> 1:3.0
1:3.1 -> 1:3.0, 1:3.1
<protocol break without API break> 2:3.1 -> (2:3.0 which doesn't exist) and 2:3.1

Alexandros Frantzis (afrantzis) wrote :

> current client API -> supported protocol versions

epoch:current_client_API -> supported protocol versions

Alan Griffiths (alan-griffiths) wrote :

> epoch:current_client_API -> supported protocol versions

OK, added epoch

Alexandros Frantzis (afrantzis) wrote :

Looks good.

review: Approve
Kevin DuBois (kdub) wrote :

still lgtm

Daniel van Vugt (vanvugt) wrote :

Tested: Client built from this branch, server built from lp:mir/0.14.

Indeed the hang is now replaced with an inexplicable failure to connect. Even mir_connection_get_error_message() just gives "Connect failed". In a way this is kind of worse, because the user will be even more confused than before.

We need a custom error message so clients that bother to call mir_connection_get_error_message() will know why they are failing and there really is a server at the other end that's just rejecting us.

review: Needs Fixing
Alan Griffiths (alan-griffiths) wrote :

> Tested: Client built from this branch, server built from lp:mir/0.14.
>
> Indeed the hang is now replaced with an inexplicable failure to connect. Even
> mir_connection_get_error_message() just gives "Connect failed". In a way this
> is kind of worse, because the user will be even more confused than before.
>
> We need a custom error message so clients that bother to call
> mir_connection_get_error_message() will know why they are failing and there
> really is a server at the other end that's just rejecting us.

All that the client sees is the connection being dropped. Even if we were to change that handshake in current servers it wouldn't help with 0.14 servers which just close the socket.

There are other failure modes, e.g.

"Failed to connect to server `/tmp/mir_socket': Failed to connect to server socket: Permission denied"

So I've updated the message to:

"Failed to connect to server `/tmp/mir_socket': Failed to connect: not accepted by server"

Which is a little less opaque but not specific beyond what the client really knows. (E.g. it could be the SessionAuthorizer that forces the disconnect.)

Daniel van Vugt (vanvugt) wrote :

OK, that will work.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/client/mir_connection.cpp'
2--- src/client/mir_connection.cpp 2015-08-21 11:36:32 +0000
3+++ src/client/mir_connection.cpp 2015-08-28 13:15:15 +0000
4@@ -165,7 +165,7 @@
5 {
6 std::lock_guard<decltype(mutex)> lock(mutex);
7
8- if (connect_result && connect_result->has_error())
9+ if (error_message.empty() && connect_result)
10 {
11 return connect_result->error().c_str();
12 }
13@@ -259,7 +259,7 @@
14 std::lock_guard<decltype(mutex)> lock(mutex);
15
16 if (!connect_result->has_platform() || !connect_result->has_display_configuration())
17- set_error_message("Connect failed");
18+ set_error_message("Failed to connect: not accepted by server");
19
20 connect_done = true;
21
22
23=== modified file 'src/client/rpc/mir_basic_rpc_channel.cpp'
24--- src/client/rpc/mir_basic_rpc_channel.cpp 2015-07-29 01:39:52 +0000
25+++ src/client/rpc/mir_basic_rpc_channel.cpp 2015-08-28 13:15:15 +0000
26@@ -23,12 +23,33 @@
27 #include "mir_protobuf_wire.pb.h"
28 #include "mir/frontend/client_constants.h"
29 #include "mir/variable_length_array.h"
30+#include "mir/protobuf/protocol_version.h"
31+#include "mir/log.h"
32
33 #include <sstream>
34
35 namespace mclr = mir::client::rpc;
36 namespace mclrd = mir::client::rpc::detail;
37
38+namespace
39+{
40+int get_protocol_version()
41+{
42+ auto protocol_version = mir::protobuf::current_protocol_version();
43+
44+ if (auto const protocol_version_override = getenv("MIR_CLIENT_TEST_OVERRRIDE_PROTOCOL_VERSION"))
45+ {
46+ int const new_protcol_version = strtol(protocol_version_override, nullptr, 0);
47+ mir::log(mir::logging::Severity::warning, MIR_LOG_COMPONENT,
48+ "Overriding protocol version 0x%x with 0x%x", protocol_version, new_protcol_version);
49+
50+ protocol_version = new_protcol_version;
51+ }
52+
53+ return protocol_version;
54+}
55+}
56+
57 mclrd::PendingCallCache::PendingCallCache(
58 std::shared_ptr<RpcReport> const& rpc_report)
59 : rpc_report{rpc_report}
60@@ -99,7 +120,8 @@
61
62
63 mclr::MirBasicRpcChannel::MirBasicRpcChannel() :
64- next_message_id(0)
65+ next_message_id(0),
66+ protocol_version{get_protocol_version()}
67 {
68 }
69
70@@ -120,7 +142,7 @@
71 invoke.set_id(next_id());
72 invoke.set_method_name(method_name);
73 invoke.set_parameters(buffer.data(), buffer.size());
74- invoke.set_protocol_version(1);
75+ invoke.set_protocol_version(protocol_version);
76 invoke.set_side_channel_fds(num_side_channel_fds);
77
78 return invoke;
79
80=== modified file 'src/client/rpc/mir_basic_rpc_channel.h'
81--- src/client/rpc/mir_basic_rpc_channel.h 2015-07-29 01:39:52 +0000
82+++ src/client/rpc/mir_basic_rpc_channel.h 2015-08-28 13:15:15 +0000
83@@ -118,6 +118,7 @@
84
85 private:
86 std::atomic<int> next_message_id;
87+ int const protocol_version;
88 };
89
90 }
91
92=== added file 'src/include/common/mir/protobuf/protocol_version.h'
93--- src/include/common/mir/protobuf/protocol_version.h 1970-01-01 00:00:00 +0000
94+++ src/include/common/mir/protobuf/protocol_version.h 2015-08-28 13:15:15 +0000
95@@ -0,0 +1,49 @@
96+/*
97+ * Copyright © 2015 Canonical Ltd.
98+ *
99+ * This program is free software: you can redistribute it and/or modify it
100+ * under the terms of the GNU Lesser General Public License version 3,
101+ * as published by the Free Software Foundation.
102+ *
103+ * This program is distributed in the hope that it will be useful,
104+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
105+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
106+ * GNU Lesser General Public License for more details.
107+ *
108+ * You should have received a copy of the GNU Lesser General Public License
109+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
110+ *
111+ * Authored by: Alan Griffiths <alan@octopull.co.uk>
112+ */
113+
114+#ifndef MIR_PROTOBUF_PROTOCOL_VERSION_H
115+#define MIR_PROTOBUF_PROTOCOL_VERSION_H
116+
117+#include "mir_toolkit/version.h"
118+
119+namespace mir
120+{
121+namespace protobuf
122+{
123+// For the present we use the client API protocol_version as a proxy for the protocol
124+// version as the protocol is typically updated to support API changes.
125+// If we need to break protocol without a corresponding ABI break then we need to bump "epoch"
126+inline constexpr int protocol_version(int major, int minor, int epoch = 0)
127+{
128+ return MIR_VERSION_NUMBER(epoch, major, minor);
129+}
130+
131+inline constexpr int current_protocol_version()
132+{
133+ return protocol_version(MIR_CLIENT_MAJOR_VERSION, MIR_CLIENT_MINOR_VERSION);
134+}
135+
136+// Client libraries older than last ABI break may make calls we can't understand
137+inline constexpr int oldest_compatible_protocol_version()
138+{
139+ return protocol_version(MIR_CLIENT_MAJOR_VERSION, 0);
140+}
141+}
142+}
143+
144+#endif //MIR_PROTOBUF_PROTOCOL_VERSION_H
145
146=== modified file 'src/server/frontend/socket_connection.cpp'
147--- src/server/frontend/socket_connection.cpp 2015-02-22 07:46:25 +0000
148+++ src/server/frontend/socket_connection.cpp 2015-08-28 13:15:15 +0000
149@@ -21,6 +21,7 @@
150 #include "message_receiver.h"
151 #include "mir/frontend/message_processor.h"
152 #include "mir/frontend/session_credentials.h"
153+#include "mir/protobuf/protocol_version.h"
154
155 #include "mir_protobuf_wire.pb.h"
156
157@@ -90,6 +91,10 @@
158 void mfd::SocketConnection::on_new_message(const boost::system::error_code& error)
159 try
160 {
161+ // No support for newer client libraries
162+ static auto const forward_compatibility_limit = mir::protobuf::current_protocol_version();
163+ static auto const backward_compatibility_limit = mir::protobuf::oldest_compatible_protocol_version();
164+
165 if (error)
166 {
167 BOOST_THROW_EXCEPTION(std::runtime_error(error.message()));
168@@ -98,7 +103,9 @@
169 mir::protobuf::wire::Invocation invocation;
170 invocation.ParseFromArray(body.data(), body.size());
171
172- if (!invocation.has_protocol_version() || invocation.protocol_version() != 1)
173+ if (!invocation.has_protocol_version() ||
174+ invocation.protocol_version() > forward_compatibility_limit ||
175+ invocation.protocol_version() < backward_compatibility_limit)
176 BOOST_THROW_EXCEPTION(std::runtime_error("Unsupported protocol version"));
177
178 std::vector<mir::Fd> fds;
179
180=== modified file 'tests/acceptance-tests/test_client_library.cpp'
181--- tests/acceptance-tests/test_client_library.cpp 2015-07-27 07:24:50 +0000
182+++ tests/acceptance-tests/test_client_library.cpp 2015-08-28 13:15:15 +0000
183@@ -135,7 +135,11 @@
184 }
185
186 mtf::UsingStubClientPlatform using_stub_client_platform;
187+
188+ static auto constexpr current_protocol_epoch = 0;
189 };
190+
191+auto const* const protocol_version_override = "MIR_CLIENT_TEST_OVERRRIDE_PROTOCOL_VERSION";
192 }
193
194 using namespace testing;
195@@ -164,6 +168,67 @@
196 mir_connection_release(connection);
197 }
198
199+TEST_F(ClientLibrary, connects_when_protobuf_protocol_oldest_supported)
200+{
201+ std::ostringstream buffer;
202+ buffer << MIR_VERSION_NUMBER(current_protocol_epoch, MIR_CLIENT_MAJOR_VERSION, 0);
203+
204+ add_to_environment(protocol_version_override, buffer.str().c_str());
205+
206+ connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
207+
208+ EXPECT_THAT(connection, NotNull());
209+ EXPECT_TRUE(mir_connection_is_valid(connection));
210+ EXPECT_THAT(mir_connection_get_error_message(connection), StrEq(""));
211+
212+ mir_connection_release(connection);
213+}
214+
215+TEST_F(ClientLibrary, reports_error_when_protobuf_protocol_obsolete)
216+{
217+ std::ostringstream buffer;
218+ buffer << MIR_VERSION_NUMBER(current_protocol_epoch, MIR_CLIENT_MAJOR_VERSION-1, 0);
219+ add_to_environment(protocol_version_override, buffer.str().c_str());
220+
221+ connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
222+
223+ EXPECT_THAT(connection, NotNull());
224+ EXPECT_FALSE(mir_connection_is_valid(connection));
225+ EXPECT_THAT(mir_connection_get_error_message(connection), HasSubstr("not accepted by server"));
226+
227+ mir_connection_release(connection);
228+}
229+
230+TEST_F(ClientLibrary, reports_error_when_protobuf_protocol_too_new)
231+{
232+ std::ostringstream buffer;
233+ buffer << MIR_VERSION_NUMBER(current_protocol_epoch, MIR_CLIENT_MAJOR_VERSION, MIR_CLIENT_MINOR_VERSION+1);
234+ add_to_environment(protocol_version_override, buffer.str().c_str());
235+
236+ connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
237+
238+ EXPECT_THAT(connection, NotNull());
239+ EXPECT_FALSE(mir_connection_is_valid(connection));
240+ EXPECT_THAT(mir_connection_get_error_message(connection), HasSubstr("not accepted by server"));
241+
242+ mir_connection_release(connection);
243+}
244+
245+TEST_F(ClientLibrary, reports_error_when_protobuf_protocol_epoch_too_new)
246+{
247+ std::ostringstream buffer;
248+ buffer << MIR_VERSION_NUMBER(current_protocol_epoch+1, MIR_CLIENT_MAJOR_VERSION, MIR_CLIENT_MINOR_VERSION);
249+ add_to_environment(protocol_version_override, buffer.str().c_str());
250+
251+ connection = mir_connect_sync(new_connection().c_str(), __PRETTY_FUNCTION__);
252+
253+ EXPECT_THAT(connection, NotNull());
254+ EXPECT_FALSE(mir_connection_is_valid(connection));
255+ EXPECT_THAT(mir_connection_get_error_message(connection), HasSubstr("not accepted by server"));
256+
257+ mir_connection_release(connection);
258+}
259+
260 TEST_F(ClientLibrary, creates_surface)
261 {
262 mir_wait_for(mir_connect(new_connection().c_str(), __PRETTY_FUNCTION__, connection_callback, this));
263
264=== modified file 'tests/integration-tests/test_error_reporting.cpp'
265--- tests/integration-tests/test_error_reporting.cpp 2015-07-29 01:18:29 +0000
266+++ tests/integration-tests/test_error_reporting.cpp 2015-08-28 13:15:15 +0000
267@@ -100,7 +100,7 @@
268
269 ASSERT_TRUE(connection != NULL);
270 EXPECT_FALSE(mir_connection_is_valid(connection));
271- EXPECT_THAT(mir_connection_get_error_message(connection), testing::HasSubstr(test_exception_text));
272+ EXPECT_THAT(mir_connection_get_error_message(connection), testing::HasSubstr("Failed to connect"));
273
274 mir_connection_release(connection);
275 }
276
277=== modified file 'tests/unit-tests/frontend/test_socket_connection.cpp'
278--- tests/unit-tests/frontend/test_socket_connection.cpp 2015-06-25 03:00:08 +0000
279+++ tests/unit-tests/frontend/test_socket_connection.cpp 2015-08-28 13:15:15 +0000
280@@ -22,6 +22,7 @@
281 #include "mir/frontend/message_processor.h"
282 #include "mir/frontend/session_credentials.h"
283 #include "mir/fd.h"
284+#include "mir/protobuf/protocol_version.h"
285
286 #include "mir/test/fake_shared.h"
287
288@@ -145,7 +146,7 @@
289 invocation.set_id(1);
290 invocation.set_method_name("");
291 invocation.set_parameters(buffer, 0);
292- invocation.set_protocol_version(1);
293+ invocation.set_protocol_version(mir::protobuf::current_protocol_version());
294 invocation.set_side_channel_fds(2);
295 auto const body_size = invocation.ByteSize();
296 buffer[0] = body_size / 0x100;

Subscribers

People subscribed via source and target branches