Mir

Merge lp:~vanvugt/mir/fix-1527449-Plugin into lp:mir

Proposed by Daniel van Vugt
Status: Rejected
Rejected by: Alan Griffiths
Proposed branch: lp:~vanvugt/mir/fix-1527449-Plugin
Merge into: lp:mir
Diff against target: 645 lines (+333/-43)
13 files modified
include/common/mir/plugin.h (+48/-0)
src/client/default_connection_configuration.cpp (+13/-21)
src/client/mir_connection.cpp (+1/-0)
src/client/mir_connection.h (+3/-0)
src/client/probing_client_platform_factory.cpp (+44/-5)
src/client/probing_client_platform_factory.h (+11/-2)
src/common/sharedlibrary/CMakeLists.txt (+1/-0)
src/common/sharedlibrary/plugin.cpp (+50/-0)
src/common/symbols.map (+6/-0)
src/include/client/mir/client_platform.h (+2/-1)
src/platforms/mesa/client/client_platform_factory.cpp (+7/-3)
tests/mir_test_framework/CMakeLists.txt (+1/-0)
tests/unit-tests/client/test_probing_client_platform_factory.cpp (+146/-11)
To merge this branch: bzr merge lp:~vanvugt/mir/fix-1527449-Plugin
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Mir CI Bot continuous-integration Approve
Kevin DuBois (community) Approve
Review via email: mp+283168@code.launchpad.net

Commit message

Avoid leaking client platform modules, and particularly ensure unused
modules are not kept resident while the client is running (LP: #1527449)

This also fixes crashing clients (LP: #1526658) due to Mesa's dlsym calls landing on the wrong symbol versions. However that fix only works for future Mir releases and not retrospectively because of the dlopen leak in client_platform_factory.cpp, which will forever exist in mesa.so.[23]

Description of the change

This is the Plugin version of the fix, which works reliably but is less popular.

Alternative:
https://code.launchpad.net/~vanvugt/mir/fix-1527449-make_module_ptr-2/+merge/283378

To post a comment you must log in.
Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3262
https://mir-jenkins.ubuntu.com/job/mir-ci/85/
Executed test runs:
    None: https://mir-jenkins.ubuntu.com/job/generic-update-mp/85/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/85/rebuild

review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

LGTM, nonblocking:

+ // Must never be inlined!
+ Plugin();

could __attribute__ (noinline) be used?

+ auto modules = mir::libraries_for_path(path,
+ *shared_library_prober_report);
could be one line.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:3262
http://jenkins.qa.ubuntu.com/job/mir-ci/6056/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-android-vivid-i386-build/5588
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-clang-vivid-amd64-build/4495
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-vivid-touch/5544
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-xenial-touch/298
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/381
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/381/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/381
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/381/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5541
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5541/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-touch/8000
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26785
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/294
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/294/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-xenial-touch/150
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26795

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/mir-ci/6056/rebuild

review: Approve (continuous-integration)
lp:~vanvugt/mir/fix-1527449-Plugin updated
3263. By Daniel van Vugt

Merge latest trunk

3264. By Daniel van Vugt

Merge latest trunk and fix a conflict.

Revision history for this message
Mir CI Bot (mir-ci-bot) wrote :

PASSED: Continuous integration, rev:3264
https://mir-jenkins.ubuntu.com/job/mir-ci/99/
Executed test runs:
    None: https://mir-jenkins.ubuntu.com/job/generic-update-mp/99/console

Click here to trigger a rebuild:
https://mir-jenkins.ubuntu.com/job/mir-ci/99/rebuild

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

PASSED: Continuous integration, rev:3264
http://jenkins.qa.ubuntu.com/job/mir-ci/6073/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-android-vivid-i386-build/5610
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-clang-vivid-amd64-build/4517
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-vivid-touch/5566
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-xenial-touch/305
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/397
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/397/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/397
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/397/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5563
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5563/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-touch/8013
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26833
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/301
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/301/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-xenial-touch/157
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26844

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/mir-ci/6073/rebuild

review: Approve (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

We seem to be landing the alternate

Unmerged revisions

3264. By Daniel van Vugt

Merge latest trunk and fix a conflict.

3263. By Daniel van Vugt

Merge latest trunk

3262. By Daniel van Vugt

Remove redundant platform initialization added recently that was tripping
the destruction check.

3261. By Daniel van Vugt

Continue reverting back to Plugin

3260. By Daniel van Vugt

Start reverting back to the Plugin approach

3259. By Daniel van Vugt

Merge latest trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'include/common/mir/plugin.h'
2--- include/common/mir/plugin.h 1970-01-01 00:00:00 +0000
3+++ include/common/mir/plugin.h 2016-01-20 16:21:34 +0000
4@@ -0,0 +1,48 @@
5+/*
6+ * Copyright © 2015 Canonical Ltd.
7+ *
8+ * This program is free software: you can redistribute it and/or modify it
9+ * under the terms of the GNU Lesser General Public License version 3,
10+ * as published by the Free Software Foundation.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU Lesser General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU Lesser General Public License
18+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
19+ *
20+ * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
21+ */
22+
23+#ifndef MIR_PLUGIN_H_
24+#define MIR_PLUGIN_H_
25+
26+#include <memory>
27+
28+namespace mir
29+{
30+
31+/**
32+ * Plugin is a base class designed to underpin any class whose implementation
33+ * exists in a dynamically run-time loaded library.
34+ * Plugin provides a foolproof check that you don't accidentally unload the
35+ * library whose code you're still executing.
36+ */
37+class Plugin
38+{
39+public:
40+ // Must never be inlined!
41+ Plugin();
42+ virtual ~Plugin() noexcept;
43+
44+ void keep_library_loaded(std::shared_ptr<void> const&);
45+ std::shared_ptr<void> keep_library_loaded() const;
46+private:
47+ std::shared_ptr<void> library;
48+};
49+
50+} // namespace mir
51+
52+#endif // MIR_PLUGIN_H_
53
54=== modified file 'src/client/default_connection_configuration.cpp'
55--- src/client/default_connection_configuration.cpp 2015-12-23 08:13:34 +0000
56+++ src/client/default_connection_configuration.cpp 2016-01-20 16:21:34 +0000
57@@ -34,11 +34,9 @@
58 #include "lttng/shared_library_prober_report.h"
59 #include "connection_surface_map.h"
60 #include "lifecycle_control.h"
61-#include "mir/shared_library.h"
62 #include "mir/client_platform_factory.h"
63 #include "probing_client_platform_factory.h"
64 #include "mir_event_distributor.h"
65-#include "mir/shared_library_prober.h"
66
67 namespace mcl = mir::client;
68
69@@ -47,10 +45,6 @@
70 std::string const off_opt_val{"off"};
71 std::string const log_opt_val{"log"};
72 std::string const lttng_opt_val{"lttng"};
73-
74-// Shove this here until we properly manage the lifetime of our
75-// loadable modules
76-std::shared_ptr<mcl::ProbingClientPlatformFactory> the_platform_prober;
77 }
78
79 mcl::DefaultConnectionConfiguration::DefaultConnectionConfiguration(
80@@ -95,22 +89,20 @@
81 return client_platform_factory(
82 [this]
83 {
84- auto const platform_override = getenv("MIR_CLIENT_PLATFORM_LIB");
85- std::vector<std::shared_ptr<mir::SharedLibrary>> platform_plugins;
86- if (platform_override)
87- {
88- platform_plugins.push_back(std::make_shared<mir::SharedLibrary>(platform_override));
89- }
90+ std::vector<std::string> libs, paths;
91+ if (auto const lib = getenv("MIR_CLIENT_PLATFORM_LIB"))
92+ libs.push_back(lib);
93+
94+ if (auto path = getenv("MIR_CLIENT_PLATFORM_PATH"))
95+ paths.push_back(path);
96 else
97- {
98- auto const platform_path_override = getenv("MIR_CLIENT_PLATFORM_PATH");
99- auto const platform_path = platform_path_override ? platform_path_override : MIR_CLIENT_PLATFORM_PATH;
100- platform_plugins = mir::libraries_for_path(platform_path, *the_shared_library_prober_report());
101- }
102-
103- the_platform_prober = std::make_shared<mcl::ProbingClientPlatformFactory>(platform_plugins);
104-
105- return the_platform_prober;
106+ paths.push_back(MIR_CLIENT_PLATFORM_PATH);
107+
108+ return std::make_shared<mcl::ProbingClientPlatformFactory>(
109+ the_shared_library_prober_report(),
110+ libs,
111+ paths
112+ );
113 });
114 }
115
116
117=== modified file 'src/client/mir_connection.cpp'
118--- src/client/mir_connection.cpp 2016-01-20 00:44:40 +0000
119+++ src/client/mir_connection.cpp 2016-01-20 16:21:34 +0000
120@@ -521,6 +521,7 @@
121 };
122
123 platform = client_platform_factory->create_client_platform(this);
124+ platform_library = platform->keep_library_loaded();
125 native_display = platform->create_egl_native_display();
126 display_configuration->set_configuration(connect_result->display_configuration());
127 lifecycle_control->set_callback(default_lifecycle_event_handler);
128
129=== modified file 'src/client/mir_connection.h'
130--- src/client/mir_connection.h 2016-01-18 15:54:40 +0000
131+++ src/client/mir_connection.h 2016-01-20 16:21:34 +0000
132@@ -216,6 +216,9 @@
133
134 mutable std::mutex mutex; // Protects all members of *this (except release_wait_handles)
135
136+ // Destruct this last as it will unmap the platform library from memory.
137+ std::shared_ptr<void> platform_library;
138+
139 std::shared_ptr<mir::client::ConnectionSurfaceMap> surface_map;
140 std::shared_ptr<mir::client::rpc::MirBasicRpcChannel> const channel;
141 mir::client::rpc::DisplayServer server;
142
143=== modified file 'src/client/probing_client_platform_factory.cpp'
144--- src/client/probing_client_platform_factory.cpp 2016-01-05 10:23:12 +0000
145+++ src/client/probing_client_platform_factory.cpp 2016-01-20 16:21:34 +0000
146@@ -1,23 +1,60 @@
147 #include "probing_client_platform_factory.h"
148+#include "mir/client_platform.h"
149+#include "mir/shared_library.h"
150+#include "mir/shared_library_prober.h"
151+#include "mir/shared_library_prober_report.h"
152
153 #include <boost/exception/all.hpp>
154 #include <stdexcept>
155
156 namespace mcl = mir::client;
157
158-mcl::ProbingClientPlatformFactory::ProbingClientPlatformFactory(std::vector<std::shared_ptr<mir::SharedLibrary>> const& modules)
159- : platform_modules{modules}
160+mcl::ProbingClientPlatformFactory::ProbingClientPlatformFactory(
161+ std::shared_ptr<mir::SharedLibraryProberReport> const& rep,
162+ StringList const& force_libs,
163+ StringList const& lib_paths)
164+ : shared_library_prober_report{rep},
165+ platform_overrides{force_libs},
166+ platform_paths{lib_paths}
167 {
168- if (modules.empty())
169+ if (platform_overrides.empty() && platform_paths.empty())
170 {
171 BOOST_THROW_EXCEPTION(
172- std::runtime_error{"Attempted to create a ClientPlatformFactory with no platform modules"});
173+ std::runtime_error{"Attempted to create a ClientPlatformFactory with no platform paths or overrides"});
174 }
175 }
176
177 std::shared_ptr<mcl::ClientPlatform>
178 mcl::ProbingClientPlatformFactory::create_client_platform(mcl::ClientContext* context)
179 {
180+ // Note we don't want to keep unused platform modules loaded any longer
181+ // than it takes to choose the right one. So this list is local:
182+ std::vector<std::shared_ptr<mir::SharedLibrary>> platform_modules;
183+
184+ if (!platform_overrides.empty())
185+ {
186+ // Even forcing a choice, platform is only loaded on demand. It's good
187+ // to not need to hold the module open when there are no connections.
188+ // Also, this allows you to swap driver binaries on disk at run-time,
189+ // if you really wanted to.
190+
191+ for (auto const& platform : platform_overrides)
192+ platform_modules.push_back(
193+ std::make_shared<mir::SharedLibrary>(platform));
194+ }
195+ else
196+ {
197+ for (auto const& path : platform_paths)
198+ {
199+ // This is sub-optimal. We shouldn't need to have all drivers
200+ // loaded simultaneously, but we do for now due to this API:
201+ auto modules = mir::libraries_for_path(path,
202+ *shared_library_prober_report);
203+ for (auto const& module : modules)
204+ platform_modules.push_back(module);
205+ }
206+ }
207+
208 for (auto& module : platform_modules)
209 {
210 try
211@@ -26,7 +63,9 @@
212 if (probe(context))
213 {
214 auto factory = module->load_function<mir::client::CreateClientPlatform>("create_client_platform", CLIENT_PLATFORM_VERSION);
215- return factory(context);
216+ auto platform = factory(context);
217+ platform->keep_library_loaded(module);
218+ return platform;
219 }
220 }
221 catch(std::runtime_error const&)
222
223=== modified file 'src/client/probing_client_platform_factory.h'
224--- src/client/probing_client_platform_factory.h 2015-01-21 07:34:50 +0000
225+++ src/client/probing_client_platform_factory.h 2016-01-20 16:21:34 +0000
226@@ -8,16 +8,25 @@
227
228 namespace mir
229 {
230+class SharedLibraryProberReport;
231+
232 namespace client
233 {
234 class ProbingClientPlatformFactory : public ClientPlatformFactory
235 {
236 public:
237- ProbingClientPlatformFactory(std::vector<std::shared_ptr<SharedLibrary>> const& modules);
238+ using StringList = std::vector<std::string>;
239+ ProbingClientPlatformFactory(
240+ std::shared_ptr<mir::SharedLibraryProberReport> const& rep,
241+ StringList const& force_libs,
242+ StringList const& lib_paths);
243
244 std::shared_ptr<ClientPlatform> create_client_platform(ClientContext *context) override;
245+
246 private:
247- std::vector<std::shared_ptr<SharedLibrary>> platform_modules;
248+ std::shared_ptr<mir::SharedLibraryProberReport> const shared_library_prober_report;
249+ StringList const platform_overrides;
250+ StringList const platform_paths;
251 };
252
253 }
254
255=== modified file 'src/common/sharedlibrary/CMakeLists.txt'
256--- src/common/sharedlibrary/CMakeLists.txt 2015-06-17 05:20:42 +0000
257+++ src/common/sharedlibrary/CMakeLists.txt 2016-01-20 16:21:34 +0000
258@@ -18,6 +18,7 @@
259 module_deleter.cpp
260 shared_library.cpp
261 shared_library_prober.cpp
262+ plugin.cpp
263 )
264
265 list(APPEND MIR_COMMON_SOURCES
266
267=== added file 'src/common/sharedlibrary/plugin.cpp'
268--- src/common/sharedlibrary/plugin.cpp 1970-01-01 00:00:00 +0000
269+++ src/common/sharedlibrary/plugin.cpp 2016-01-20 16:21:34 +0000
270@@ -0,0 +1,50 @@
271+/*
272+ * Copyright © 2015 Canonical Ltd.
273+ *
274+ * This program is free software: you can redistribute it and/or modify it
275+ * under the terms of the GNU Lesser General Public License version 3,
276+ * as published by the Free Software Foundation.
277+ *
278+ * This program is distributed in the hope that it will be useful,
279+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
280+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
281+ * GNU Lesser General Public License for more details.
282+ *
283+ * You should have received a copy of the GNU Lesser General Public License
284+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
285+ *
286+ * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
287+ */
288+
289+#include "mir/plugin.h"
290+#include "mir/fatal.h"
291+
292+namespace mir
293+{
294+
295+Plugin::Plugin()
296+{
297+}
298+
299+Plugin::~Plugin() noexcept
300+{
301+ // This is not a recoverable error. If we don't exit now, we'll just
302+ // end up with a worse crash where we have unloaded the library we're
303+ // executing in, and so get no stack trace at all...
304+ if (library.unique())
305+ mir::fatal_error_abort("Attempted to unload a plugin library "
306+ "while it's still in use (~Plugin not "
307+ "completed yet)");
308+}
309+
310+void Plugin::keep_library_loaded(std::shared_ptr<void> const& lib)
311+{
312+ library = lib;
313+}
314+
315+std::shared_ptr<void> Plugin::keep_library_loaded() const
316+{
317+ return library;
318+}
319+
320+} // namespace mir
321
322=== modified file 'src/common/symbols.map'
323--- src/common/symbols.map 2016-01-08 09:50:51 +0000
324+++ src/common/symbols.map 2016-01-20 16:21:34 +0000
325@@ -208,6 +208,12 @@
326 typeinfo?for?mir::time::SteadyClock;
327 typeinfo?for?android::InputChannel;
328 typeinfo?for?mir::logging::DumbConsoleLogger;
329+
330 mir::detail::libname_impl*;
331+
332+ typeinfo?for?mir::Plugin;
333+ mir::Plugin::Plugin*;
334+ mir::Plugin::?Plugin*;
335+ mir::Plugin::keep_library_loaded*;
336 };
337 } MIR_COMMON_5.1;
338
339=== modified file 'src/include/client/mir/client_platform.h'
340--- src/include/client/mir/client_platform.h 2015-12-23 06:41:56 +0000
341+++ src/include/client/mir/client_platform.h 2016-01-20 16:21:34 +0000
342@@ -21,6 +21,7 @@
343 #include "mir/graphics/native_buffer.h"
344 #include "mir_toolkit/client_types.h"
345 #include "mir_toolkit/mir_native_buffer.h"
346+#include "mir/plugin.h"
347
348 #include <EGL/eglplatform.h>
349 #include <EGL/egl.h> // for EGLConfig
350@@ -38,7 +39,7 @@
351 * Interface to client-side platform specific support for graphics operations.
352 * \ingroup platform_enablement
353 */
354-class ClientPlatform
355+class ClientPlatform : public mir::Plugin
356 {
357 public:
358 ClientPlatform() = default;
359
360=== modified file 'src/platforms/mesa/client/client_platform_factory.cpp'
361--- src/platforms/mesa/client/client_platform_factory.cpp 2015-11-25 20:26:59 +0000
362+++ src/platforms/mesa/client/client_platform_factory.cpp 2016-01-20 16:21:34 +0000
363@@ -35,14 +35,18 @@
364
365 namespace
366 {
367-// Hack around the way mesa loads mir: This hack makes the
368-// necessary symbols global.
369+// Re-export our own symbols from mesa.so.N globally so that Mesa itself can
370+// find them with a simple dlsym(NULL,)
371 void ensure_loaded_with_rtld_global_mesa_client()
372 {
373 Dl_info info;
374
375 dladdr(reinterpret_cast<void*>(&ensure_loaded_with_rtld_global_mesa_client), &info);
376- dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
377+ void* reexport_self_global =
378+ dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
379+ // Yes, RTLD_NOLOAD does increase the ref count. So dlclose...
380+ if (reexport_self_global)
381+ dlclose(reexport_self_global);
382 }
383
384 struct RealBufferFileOps : public mclm::BufferFileOps
385
386=== modified file 'tests/mir_test_framework/CMakeLists.txt'
387--- tests/mir_test_framework/CMakeLists.txt 2016-01-05 19:14:36 +0000
388+++ tests/mir_test_framework/CMakeLists.txt 2016-01-20 16:21:34 +0000
389@@ -93,6 +93,7 @@
390 mirclientplatformstub
391
392 mir-test-framework-static
393+ mircommon
394 ${UMOCKDEV_LDFLAGS} ${UMOCKDEV_LIBRARIES}
395 )
396
397
398=== modified file 'tests/unit-tests/client/test_probing_client_platform_factory.cpp'
399--- tests/unit-tests/client/test_probing_client_platform_factory.cpp 2015-09-30 19:34:21 +0000
400+++ tests/unit-tests/client/test_probing_client_platform_factory.cpp 2016-01-20 16:21:34 +0000
401@@ -18,6 +18,7 @@
402
403 #include "mir/client_platform.h"
404 #include "src/client/probing_client_platform_factory.h"
405+#include "src/server/report/null_report_factory.h"
406
407 #include "mir/test/doubles/mock_client_context.h"
408 #include "mir_test_framework/executable_path.h"
409@@ -25,30 +26,57 @@
410
411 #include <gmock/gmock.h>
412 #include <gtest/gtest.h>
413+#include <dlfcn.h>
414
415 namespace mtf = mir_test_framework;
416 namespace mtd = mir::test::doubles;
417
418 namespace
419 {
420-std::vector<std::shared_ptr<mir::SharedLibrary>>
421+std::vector<std::string>
422 all_available_modules()
423 {
424- std::vector<std::shared_ptr<mir::SharedLibrary>> modules;
425+ std::vector<std::string> modules;
426 #if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)
427- modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("mesa")));
428+ modules.push_back(mtf::client_platform("mesa"));
429 #endif
430 #ifdef MIR_BUILD_PLATFORM_ANDROID
431- modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("android")));
432+ modules.push_back(mtf::client_platform("android"));
433 #endif
434 return modules;
435 }
436+
437+bool loaded(std::string const& path)
438+{
439+ void* x = dlopen(path.c_str(), RTLD_LAZY | RTLD_NOLOAD);
440+ if (x)
441+ dlclose(x);
442+ return !!x;
443+}
444+
445+void populate_valid(MirPlatformPackage& pkg)
446+{
447+ memset(&pkg, 0, sizeof(MirPlatformPackage));
448+ pkg.fd_items = 1;
449+ pkg.fd[0] = 23;
450+}
451+
452+void safely_unload(std::shared_ptr<mir::client::ClientPlatform>& platform)
453+{
454+ ASSERT_TRUE(platform.unique());
455+ auto library = platform->keep_library_loaded();
456+ platform.reset();
457+ library.reset();
458+}
459+
460 }
461
462 TEST(ProbingClientPlatformFactory, ThrowsErrorWhenConstructedWithNoPlatforms)
463 {
464 std::vector<std::shared_ptr<mir::SharedLibrary>> empty_modules;
465- EXPECT_THROW(mir::client::ProbingClientPlatformFactory{empty_modules},
466+ EXPECT_THROW(mir::client::ProbingClientPlatformFactory(
467+ mir::report::null_shared_library_prober_report(),
468+ {}, {}),
469 std::runtime_error);
470 }
471
472@@ -56,7 +84,10 @@
473 {
474 using namespace testing;
475
476- mir::client::ProbingClientPlatformFactory factory{all_available_modules()};
477+ mir::client::ProbingClientPlatformFactory factory(
478+ mir::report::null_shared_library_prober_report(),
479+ all_available_modules(),
480+ {});
481
482 mtd::MockClientContext context;
483 ON_CALL(context, populate_server_package(_))
484@@ -73,6 +104,98 @@
485 std::runtime_error);
486 }
487
488+TEST(ProbingClientPlatformFactory, DoesNotLeakTheUsedDriverModuleOnShutdown)
489+{ // Regression test for LP: #1527449
490+ using namespace testing;
491+ auto const modules = all_available_modules();
492+ ASSERT_FALSE(modules.empty());
493+ std::string const preferred_module = modules.front();
494+
495+ mir::client::ProbingClientPlatformFactory factory(
496+ mir::report::null_shared_library_prober_report(),
497+ {preferred_module},
498+ {});
499+
500+ std::shared_ptr<mir::client::ClientPlatform> platform;
501+ mtd::MockClientContext context;
502+ ON_CALL(context, populate_server_package(_))
503+ .WillByDefault(Invoke(populate_valid));
504+
505+ ASSERT_FALSE(loaded(preferred_module));
506+ platform = factory.create_client_platform(&context);
507+ ASSERT_TRUE(loaded(preferred_module));
508+ safely_unload(platform);
509+ EXPECT_FALSE(loaded(preferred_module));
510+}
511+
512+TEST(ProbingClientPlatformFactory, DoesNotLeakUnusedDriverModulesOnStartup)
513+{ // Regression test for LP: #1527449 and LP: #1526658
514+ using namespace testing;
515+ auto const modules = all_available_modules();
516+ ASSERT_FALSE(modules.empty());
517+
518+ // Note: This test is only really effective with nmodules>1, which many of
519+ // our builds will have. But nmodules==1 is harmless.
520+
521+ mir::client::ProbingClientPlatformFactory factory(
522+ mir::report::null_shared_library_prober_report(),
523+ modules,
524+ {});
525+
526+ std::shared_ptr<mir::client::ClientPlatform> platform;
527+ mtd::MockClientContext context;
528+ ON_CALL(context, populate_server_package(_))
529+ .WillByDefault(Invoke(populate_valid));
530+
531+ int nloaded = 0;
532+ for (auto const& m : modules)
533+ if (loaded(m)) ++nloaded;
534+ ASSERT_EQ(0, nloaded);
535+
536+ platform = factory.create_client_platform(&context);
537+
538+ nloaded = 0;
539+ for (auto const& m : modules)
540+ if (loaded(m)) ++nloaded;
541+ EXPECT_EQ(1, nloaded); // expect not assert, because we need safely_unload
542+
543+ safely_unload(platform);
544+
545+ nloaded = 0;
546+ for (auto const& m : modules)
547+ if (loaded(m)) ++nloaded;
548+ ASSERT_EQ(0, nloaded);
549+}
550+
551+// Note "DeathTest" informs our scripts that we expect a leak from the
552+// child process that died, and to ignore it.
553+TEST(ProbingClientPlatformFactoryDeathTest, DiesOnUnsafeRelease)
554+{
555+ using namespace testing;
556+ auto const modules = all_available_modules();
557+ ASSERT_FALSE(modules.empty());
558+ std::string const preferred_module = modules.front();
559+
560+ mir::client::ProbingClientPlatformFactory factory(
561+ mir::report::null_shared_library_prober_report(),
562+ {preferred_module},
563+ {});
564+
565+ std::shared_ptr<mir::client::ClientPlatform> platform;
566+ mtd::MockClientContext context;
567+ ON_CALL(context, populate_server_package(_))
568+ .WillByDefault(Invoke(populate_valid));
569+
570+ platform = factory.create_client_platform(&context);
571+
572+ // Google Test creates a child process to verify this:
573+ ASSERT_DEATH({platform.reset();}, ".*still in use.*");
574+
575+ // ... but here in the parent process we need to actively avoid the
576+ // death happening, so that our test completes:
577+ safely_unload(platform);
578+}
579+
580 #if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)
581 TEST(ProbingClientPlatformFactory, CreatesMesaPlatformWhenAppropriate)
582 #else
583@@ -81,7 +204,10 @@
584 {
585 using namespace testing;
586
587- mir::client::ProbingClientPlatformFactory factory{all_available_modules()};
588+ mir::client::ProbingClientPlatformFactory factory(
589+ mir::report::null_shared_library_prober_report(),
590+ all_available_modules(),
591+ {});
592
593 mtd::MockClientContext context;
594 ON_CALL(context, populate_server_package(_))
595@@ -95,6 +221,7 @@
596 }));
597 auto platform = factory.create_client_platform(&context);
598 EXPECT_EQ(mir_platform_type_gbm, platform->platform_type());
599+ safely_unload(platform);
600 }
601
602 #ifdef MIR_BUILD_PLATFORM_ANDROID
603@@ -105,7 +232,10 @@
604 {
605 using namespace testing;
606
607- mir::client::ProbingClientPlatformFactory factory{all_available_modules()};
608+ mir::client::ProbingClientPlatformFactory factory(
609+ mir::report::null_shared_library_prober_report(),
610+ all_available_modules(),
611+ {});
612
613 mtd::MockClientContext context;
614 ON_CALL(context, populate_server_package(_))
615@@ -118,6 +248,7 @@
616
617 auto platform = factory.create_client_platform(&context);
618 EXPECT_EQ(mir_platform_type_android, platform->platform_type());
619+ safely_unload(platform);
620 }
621
622 TEST(ProbingClientPlatformFactory, IgnoresNonClientPlatformModules)
623@@ -126,10 +257,13 @@
624
625 auto modules = all_available_modules();
626 // NOTE: For minimum fuss, load something that has minimal side-effects...
627- modules.push_back(std::make_shared<mir::SharedLibrary>("libc.so.6"));
628- modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("dummy.so")));
629+ modules.push_back("libc.so.6");
630+ modules.push_back(mtf::client_platform("dummy.so"));
631
632- mir::client::ProbingClientPlatformFactory factory{modules};
633+ mir::client::ProbingClientPlatformFactory factory(
634+ mir::report::null_shared_library_prober_report(),
635+ modules,
636+ {});
637
638 mtd::MockClientContext context;
639 ON_CALL(context, populate_server_package(_))
640@@ -139,4 +273,5 @@
641 }));
642
643 auto platform = factory.create_client_platform(&context);
644+ safely_unload(platform);
645 }

Subscribers

People subscribed via source and target branches