Mir

Merge lp:~mir-team/mir/enable-late-release into lp:mir

Proposed by Alan Griffiths
Status: Merged
Approved by: Alan Griffiths
Approved revision: no longer in the source branch.
Merged at revision: 1785
Proposed branch: lp:~mir-team/mir/enable-late-release
Merge into: lp:mir
Diff against target: 380 lines (+219/-22)
8 files modified
examples/CMakeLists.txt (+3/-0)
examples/release_at_exit.c (+109/-0)
src/client/android/android_native_display_container.cpp (+24/-2)
src/client/connection_configuration.h (+3/-1)
src/client/default_connection_configuration.cpp (+1/-3)
src/client/mesa/mesa_native_display_container.cpp (+21/-3)
src/client/mir_connection.cpp (+52/-13)
src/client/mir_connection.h (+6/-0)
To merge this branch: bzr merge lp:~mir-team/mir/enable-late-release
Reviewer Review Type Date Requested Status
Alexandros Frantzis (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Robert Carr (community) Approve
Review via email: mp+227178@code.launchpad.net

Commit message

client: don't allow shared libraries to be unloaded before platform-api calls connection_release()

Description of the change

client: don't allow shared libraries to be unloaded before platform-api calls connection_release()

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
Daniel van Vugt (vanvugt) wrote :

(1) If the client library isn't guaranteeing this is cleaned up atexit for all clients already, I think it should. That's better than having to remember to reimplement the same in every client...

26 + // We should always release our connection
27 + atexit(&close_connection);

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

> (1) If the client library isn't guaranteeing this is cleaned up atexit for all
> clients already, I think it should. That's better than having to remember to

Without this MP we require clients to release connections before the end of main().

We have one client (platform-api) that releases a connection after main() exits.

This MP adds support for the way that client behaves.

All client side resources are released when the process exits. The server will detect the dead socket and releases resources corresponding in that process. What further cleanup are you suggesting is needed?

> reimplement the same in every client...
>
> 26 + // We should always release our connection
> 27 + atexit(&close_connection);

Yes, maybe I should put the example back the way it was - this is not the canonical way to implement a client.

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

The code looks ok.

I would prefer to revert basic_client.c to its previous form. If we want to be able to try this scenario we could add a flag to release after main (or perhaps a completely different example).

Ideally, though, we would have an automated test instead. Can the problem be reproduced deterministically? Couldn't we implement a test that launches a client process that releases after main and ensure that the process shuts down properly?

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

> The code looks ok.
>
> I would prefer to revert basic_client.c to its previous form. If we want to be
> able to try this scenario we could add a flag to release after main (or
> perhaps a completely different example).
>
> Ideally, though, we would have an automated test instead. Can the problem be
> reproduced deterministically? Couldn't we implement a test that launches a
> client process that releases after main and ensure that the process shuts down
> properly?

The problem is deterministic with the hacked example code and the original implementation.

I'm not sure how easy it is within the test framework as the failure occurs as the program is exiting. I.e. after the test suite exits, after function scoped statics are deleted, and among the namespace scoped destructor executions.

For now, I'll just revert the example.

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

I've added mir_test_client_release_at_exit - which will segfault if run with the development branch libmirclient and works with this branch.

Revision history for this message
Robert Carr (robertcarr) wrote :

LGTM

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

OK. It would be very nice to have an automated test, but not a blocker for this MP.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'examples/CMakeLists.txt'
--- examples/CMakeLists.txt 2014-06-11 16:11:32 +0000
+++ examples/CMakeLists.txt 2014-07-21 16:56:41 +0000
@@ -50,6 +50,9 @@
50 ${CMAKE_THREAD_LIBS_INIT}50 ${CMAKE_THREAD_LIBS_INIT}
51)51)
5252
53add_executable(mir_test_client_release_at_exit release_at_exit.c)
54target_link_libraries(mir_test_client_release_at_exit mirclient)
55
53add_executable(mir_demo_client_multiwin multiwin.c)56add_executable(mir_demo_client_multiwin multiwin.c)
54target_link_libraries(mir_demo_client_multiwin mirclient)57target_link_libraries(mir_demo_client_multiwin mirclient)
5558
5659
=== added file 'examples/release_at_exit.c'
--- examples/release_at_exit.c 1970-01-01 00:00:00 +0000
+++ examples/release_at_exit.c 2014-07-21 16:56:41 +0000
@@ -0,0 +1,109 @@
1/*
2 * Copyright © 2012-2014 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authored by: Alan Griffiths <alan@octopull.co.uk>
17 */
18
19#include "mir_toolkit/mir_client_library.h"
20
21#include <assert.h>
22#include <string.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <getopt.h>
26
27static MirConnection *connection = 0;
28static MirSurface *surface = 0;
29
30static void connection_callback(MirConnection *new_connection, void *context)
31{
32 (void)context;
33 connection = new_connection;
34}
35
36static void surface_create_callback(MirSurface *new_surface, void *context)
37{
38 (void)context;
39 surface = new_surface;
40}
41
42static void surface_release_callback(MirSurface *old_surface, void *context)
43{
44 (void)old_surface;
45 (void)context;
46 surface = 0;
47}
48
49static void close_connection()
50{
51 if (connection)
52 {
53 mir_connection_release(connection);
54 }
55}
56
57
58void demo_client(const char* server)
59{
60 atexit(&close_connection);
61
62 mir_wait_for(mir_connect(server, __FILE__, connection_callback, NULL));
63 assert(mir_connection_is_valid(connection));
64
65 MirPixelFormat pixel_format;
66 unsigned int valid_formats;
67 mir_connection_get_available_surface_formats(connection, &pixel_format, 1, &valid_formats);
68 MirSurfaceParameters const request_params =
69 {NULL, 640, 480, pixel_format,
70 mir_buffer_usage_hardware, mir_display_output_id_invalid};
71
72 mir_wait_for(mir_connection_create_surface(connection, &request_params, surface_create_callback, NULL));
73 mir_wait_for(mir_surface_release(surface, surface_release_callback, NULL));
74}
75
76int main(int argc, char* argv[])
77{
78 // Some variables for holding command line options
79 char const *server = NULL;
80
81 // Parse the command line
82 {
83 int arg;
84 opterr = 0;
85 while ((arg = getopt (argc, argv, "hm:")) != -1)
86 {
87 switch (arg)
88 {
89 case 'm':
90 server = optarg;
91 break;
92
93 case '?':
94 case 'h':
95 default:
96 puts(argv[0]);
97 puts("Cutdown version of mir_demo_client_basic to show that");
98 puts("clients can release the connection after main() exits.");
99 puts("Usage:");
100 puts(" -m <Mir server socket>");
101 puts(" -h: this help text");
102 return -1;
103 }
104 }
105 }
106
107 demo_client(server);
108 return 0;
109}
0110
=== modified file 'src/client/android/android_native_display_container.cpp'
--- src/client/android/android_native_display_container.cpp 2013-04-24 05:22:20 +0000
+++ src/client/android/android_native_display_container.cpp 2014-07-21 16:56:41 +0000
@@ -20,13 +20,35 @@
2020
21#include "mir_toolkit/mir_client_library.h"21#include "mir_toolkit/mir_client_library.h"
2222
23#include <mutex>
24
23namespace mcl = mir::client;25namespace mcl = mir::client;
24namespace mcla = mcl::android;26namespace mcla = mcl::android;
2527
28namespace
29{
30// default_display_container needs to live until the library is unloaded
31std::mutex default_display_container_mutex;
32mcla::AndroidNativeDisplayContainer* default_display_container{nullptr};
33
34extern "C" int __attribute__((destructor)) destroy()
35{
36 std::lock_guard<std::mutex> lock(default_display_container_mutex);
37
38 delete default_display_container;
39
40 return 0;
41}
42}
43
26mcl::EGLNativeDisplayContainer& mcl::EGLNativeDisplayContainer::instance()44mcl::EGLNativeDisplayContainer& mcl::EGLNativeDisplayContainer::instance()
27{45{
28 static mcla::AndroidNativeDisplayContainer default_display_container;46 std::lock_guard<std::mutex> lock(default_display_container_mutex);
29 return default_display_container;47
48 if (!default_display_container)
49 default_display_container = new mcla::AndroidNativeDisplayContainer;
50
51 return *default_display_container;
30}52}
3153
32mcla::AndroidNativeDisplayContainer::AndroidNativeDisplayContainer()54mcla::AndroidNativeDisplayContainer::AndroidNativeDisplayContainer()
3355
=== modified file 'src/client/connection_configuration.h'
--- src/client/connection_configuration.h 2014-05-29 13:21:08 +0000
+++ src/client/connection_configuration.h 2014-07-21 16:56:41 +0000
@@ -39,8 +39,11 @@
39class Logger;39class Logger;
40}40}
4141
42class SharedLibrary;
43
42namespace client44namespace client
43{45{
46std::shared_ptr<SharedLibrary>& libraries_cache(std::string const& lib_name);
4447
45class ConnectionSurfaceMap;48class ConnectionSurfaceMap;
46class Logger;49class Logger;
@@ -70,7 +73,6 @@
70 ConnectionConfiguration(ConnectionConfiguration const&) = delete;73 ConnectionConfiguration(ConnectionConfiguration const&) = delete;
71 ConnectionConfiguration& operator=(ConnectionConfiguration const&) = delete;74 ConnectionConfiguration& operator=(ConnectionConfiguration const&) = delete;
72};75};
73
74}76}
75}77}
7678
7779
=== modified file 'src/client/default_connection_configuration.cpp'
--- src/client/default_connection_configuration.cpp 2014-05-29 13:21:08 +0000
+++ src/client/default_connection_configuration.cpp 2014-07-21 16:56:41 +0000
@@ -45,10 +45,8 @@
4545
46mir::SharedLibrary const* load_library(std::string const& libname)46mir::SharedLibrary const* load_library(std::string const& libname)
47{47{
48 // There's no point in loading twice, and it isn't safe to unload...
49 static std::map<std::string, std::shared_ptr<mir::SharedLibrary>> libraries_cache;
5048
51 if (auto& ptr = libraries_cache[libname])49 if (auto& ptr = mcl::libraries_cache(libname))
52 {50 {
53 return ptr.get();51 return ptr.get();
54 }52 }
5553
=== modified file 'src/client/mesa/mesa_native_display_container.cpp'
--- src/client/mesa/mesa_native_display_container.cpp 2014-03-06 06:05:17 +0000
+++ src/client/mesa/mesa_native_display_container.cpp 2014-07-21 16:56:41 +0000
@@ -46,12 +46,30 @@
46}46}
4747
48}48}
49}49
50// default_display_container needs to live until the library is unloaded
51std::mutex default_display_container_mutex;
52mclm::MesaNativeDisplayContainer* default_display_container{nullptr};
53
54extern "C" int __attribute__((destructor)) destroy()
55{
56 std::lock_guard<std::mutex> lock(default_display_container_mutex);
57
58 delete default_display_container;
59
60 return 0;
61}
62}
63
5064
51mcl::EGLNativeDisplayContainer& mcl::EGLNativeDisplayContainer::instance()65mcl::EGLNativeDisplayContainer& mcl::EGLNativeDisplayContainer::instance()
52{66{
53 static mclm::MesaNativeDisplayContainer default_display_container;67 std::lock_guard<std::mutex> lock(default_display_container_mutex);
54 return default_display_container;68
69 if (!default_display_container)
70 default_display_container = new mclm::MesaNativeDisplayContainer;
71
72 return *default_display_container;
55}73}
5674
57mclm::MesaNativeDisplayContainer::MesaNativeDisplayContainer()75mclm::MesaNativeDisplayContainer::MesaNativeDisplayContainer()
5876
=== modified file 'src/client/mir_connection.cpp'
--- src/client/mir_connection.cpp 2014-06-16 14:47:14 +0000
+++ src/client/mir_connection.cpp 2014-07-21 16:56:41 +0000
@@ -63,11 +63,48 @@
63 MirDisplayConfiguration* const config;63 MirDisplayConfiguration* const config;
64};64};
6565
66using LibrariesCache = std::map<std::string, std::shared_ptr<mir::SharedLibrary>>;
67
66std::mutex connection_guard;68std::mutex connection_guard;
67std::unordered_set<MirConnection*> valid_connections;69MirConnection* valid_connections{nullptr};
70// There's no point in loading twice, and it isn't safe to
71// unload while there are valid connections
72std::map<std::string, std::shared_ptr<mir::SharedLibrary>>* libraries_cache_ptr{nullptr};
73}
74
75std::shared_ptr<mir::SharedLibrary>& mcl::libraries_cache(std::string const& libname)
76{
77 std::lock_guard<std::mutex> lock(connection_guard);
78
79 if (!libraries_cache_ptr)
80 libraries_cache_ptr = new LibrariesCache;
81
82 return (*libraries_cache_ptr)[libname];
83}
84
85MirConnection::Deregisterer::~Deregisterer()
86{
87 std::lock_guard<std::mutex> lock(connection_guard);
88
89 for (auto current = &valid_connections; *current; current = &(*current)->next_valid)
90 {
91 if (self == *current)
92 {
93 *current = self->next_valid;
94 break;
95 }
96 }
97
98 // When the last valid connection goes we can clear the libraries cache
99 if (!valid_connections)
100 {
101 delete libraries_cache_ptr;
102 libraries_cache_ptr = nullptr;
103 }
68}104}
69105
70MirConnection::MirConnection(std::string const& error_message) :106MirConnection::MirConnection(std::string const& error_message) :
107 deregisterer{this},
71 channel(),108 channel(),
72 server(0),109 server(0),
73 error_message(error_message)110 error_message(error_message)
@@ -76,6 +113,7 @@
76113
77MirConnection::MirConnection(114MirConnection::MirConnection(
78 mir::client::ConnectionConfiguration& conf) :115 mir::client::ConnectionConfiguration& conf) :
116 deregisterer{this},
79 channel(conf.the_rpc_channel()),117 channel(conf.the_rpc_channel()),
80 server(channel.get(), ::google::protobuf::Service::STUB_DOESNT_OWN_CHANNEL),118 server(channel.get(), ::google::protobuf::Service::STUB_DOESNT_OWN_CHANNEL),
81 logger(conf.the_logger()),119 logger(conf.the_logger()),
@@ -89,7 +127,8 @@
89 connect_result.set_error("connect not called");127 connect_result.set_error("connect not called");
90 {128 {
91 std::lock_guard<std::mutex> lock(connection_guard);129 std::lock_guard<std::mutex> lock(connection_guard);
92 valid_connections.insert(this);130 next_valid = valid_connections;
131 valid_connections = this;
93 }132 }
94}133}
95134
@@ -99,11 +138,6 @@
99 // But, if after 500ms we don't get a call, assume it won't happen.138 // But, if after 500ms we don't get a call, assume it won't happen.
100 connect_wait_handle.wait_for_pending(std::chrono::milliseconds(500));139 connect_wait_handle.wait_for_pending(std::chrono::milliseconds(500));
101140
102 {
103 std::lock_guard<std::mutex> lock(connection_guard);
104 valid_connections.erase(this);
105 }
106
107 std::lock_guard<decltype(mutex)> lock(mutex);141 std::lock_guard<decltype(mutex)> lock(mutex);
108 if (connect_result.has_platform())142 if (connect_result.has_platform())
109 {143 {
@@ -317,13 +351,18 @@
317bool MirConnection::is_valid(MirConnection *connection)351bool MirConnection::is_valid(MirConnection *connection)
318{352{
319 {353 {
320 std::lock_guard<std::mutex> lock(connection_guard);354 std::unique_lock<std::mutex> lock(connection_guard);
321 if (valid_connections.count(connection) == 0)355 for (auto current = valid_connections; current; current = current->next_valid)
322 return false;356 {
357 if (connection == current)
358 {
359 lock.unlock();
360 std::lock_guard<decltype(connection->mutex)> lock(connection->mutex);
361 return !connection->connect_result.has_error();
362 }
363 }
323 }364 }
324365 return false;
325 std::lock_guard<decltype(connection->mutex)> lock(connection->mutex);
326 return !connection->connect_result.has_error();
327}366}
328367
329void MirConnection::populate(MirPlatformPackage& platform_package)368void MirConnection::populate(MirPlatformPackage& platform_package)
330369
=== modified file 'src/client/mir_connection.h'
--- src/client/mir_connection.h 2014-06-11 16:11:32 +0000
+++ src/client/mir_connection.h 2014-07-21 16:56:41 +0000
@@ -134,6 +134,10 @@
134 mir::protobuf::DisplayServer& display_server();134 mir::protobuf::DisplayServer& display_server();
135135
136private:136private:
137 // MUST be first data member so it is destroyed last.
138 struct Deregisterer
139 { MirConnection* const self; ~Deregisterer(); } deregisterer;
140
137 std::mutex mutex; // Protects all members of *this (except release_wait_handles)141 std::mutex mutex; // Protects all members of *this (except release_wait_handles)
138142
139 std::shared_ptr<google::protobuf::RpcChannel> const channel;143 std::shared_ptr<google::protobuf::RpcChannel> const channel;
@@ -174,6 +178,8 @@
174178
175 struct SurfaceRelease;179 struct SurfaceRelease;
176180
181 MirConnection* next_valid{nullptr};
182
177 void set_error_message(std::string const& error);183 void set_error_message(std::string const& error);
178 void done_disconnect();184 void done_disconnect();
179 void connected(mir_connected_callback callback, void * context);185 void connected(mir_connected_callback callback, void * context);

Subscribers

People subscribed via source and target branches