Mir

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

Proposed by Daniel van Vugt
Status: Merged
Merged at revision: 3258
Proposed branch: lp:~vanvugt/mir/fix-1527449
Merge into: lp:mir
Diff against target: 436 lines (+190/-42)
6 files modified
src/client/default_connection_configuration.cpp (+13/-21)
src/client/probing_client_platform_factory.cpp (+41/-4)
src/client/probing_client_platform_factory.h (+11/-2)
src/platforms/mesa/client/client_platform_factory.cpp (+9/-4)
tests/mir_test_framework/CMakeLists.txt (+1/-0)
tests/unit-tests/client/test_probing_client_platform_factory.cpp (+115/-11)
To merge this branch: bzr merge lp:~vanvugt/mir/fix-1527449
Reviewer Review Type Date Requested Status
Chris Halse Rogers Needs Fixing
PS Jenkins bot (community) continuous-integration Needs Fixing
Alan Griffiths Abstain
Mir CI Bot continuous-integration Approve
Review via email: mp+281969@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

.

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

Hmm, Valgrind's just reporting FD leaks when we die with SIGABRT. So yes, it should leak and that's not an error. I wonder why we don't see that more often...

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

PASSED: Continuous integration, rev:3249
http://jenkins.qa.ubuntu.com/job/mir-ci/5957/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-android-vivid-i386-build/5459
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-clang-vivid-amd64-build/4366
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-vivid-touch/5415
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-xenial-touch/244
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/281
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/281/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/281
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/281/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5412
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5412/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-touch/7902
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26496
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/240
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/240/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-xenial-touch/98
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26500

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

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

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

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

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

+/**
+ * Plugin is a base class designed to underpin any class whose implementation
+ * exists in a dynamically run-time loaded library.
+ * Plugin provides a foolproof check that you don't accidentally unload the
+ * library whose code you're still executing.
+ */
+class Plugin
+{
+public:
+ // Must never be inlined!
+ Plugin();
+ virtual ~Plugin() noexcept;
+
+ void keep_library_loaded(std::shared_ptr<void> const&);
+ std::shared_ptr<void> keep_library_loaded() const;
+private:
+ std::shared_ptr<void> library;
+};

This looks like an intrusive design to tackle the same problem addressed more elegantly by mir::make_module_ptr<>. Is there any reason we need both?

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

Good question. Yes, a few reasons for that:

1. I wrote Plugin long before noticing make_module_ptr existed.
2. Plugin is simpler, more elegant, smaller, fewer templates, easier to understand, and provides a handy debugging feature which tells you you've made a mistake before you encounter cryptic unmapped memory crashes.
3. AFAIK Plugin is also more reliable, as make_module_ptr is still known to cause serious problems -> bug 1528135

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

I mean Plugin vs make_module_ptr and the full contents of module_deleter.h. Although on second thoughts I think I'm finally starting to understand module_deleter.h and could make it work too.

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

OK, I switched to mir::make_module_ptr<> and got an ugly crash on shutdown. But thank you, because that led me to understand why earlier experiments over the past couple of weeks got the same crash.

The reason is that the act of constructing a mesa platform from mesa.so.4 :
   mir::make_module_ptr<mclm::ClientPlatform>(
makes the compiler instantiate that UniqueModulePtr template and its destructor inside mesa.so.4.

So indeed make_module_ptr delays the unload of mesa.so.4 a little to a safer time, it's not safe enough. Because after unloading mesa.so.4 it has to try and destroy the UniqueModulePtr whose template instance was in mesa.so.4 but was unmapped from memory just before we need to call it. Hence *crash* when using make_module_ptr.

This seems to be a fundamental flaw in make_module_ptr and might explain bug 1528135 too. But suffice to say make_module_ptr doesn't work, and will cause crashes. Whereas mir::Plugin does work without crashing.

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

The key difference and the reason why make_module_ptr doesn't work is something I documented here:

40 + // Must never be inlined!

whereas a template instance like UniqueModulePtr is instantiated in the plugin module itself. So unloading such a plugin module that implements the UniqueModulePtr will crash and can never be safe.

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

No, the problem of bug #1528135 is a bit more complex than that.

The destruction handling of UniqueModulePtr is not part of the platform library. The call to the destructor, and the unref of the library which is attached to the deleter happens at the owner of the ptr.

But of course you can annul the mechanic of make_module_ptr. Instead of making the code that loads the platform and creates instances from it the owner of the UniqueModuleptr(s).. you can stick it into a shared_ptr while you are still in the scope of the client platform. That will type erase the deleter handling, thus the shared_ptr deleter is now the owner.. and the code for it is generated inside the client platform...

To make proper use of UniqueModulePtr you need to use it as the return type of the factory functions that get dlsymed.

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

Returning UniqueModulePtr from your factory ABI within a plugin theoretically sounds like an improvement but requires a few pieces to be moved around and I'm not yet convinced it's sufficiently safe.

Forgive me for repeating but I want to restate the problem more concisely for other readers' and my own benefit:

Let's imagine you have a plugin named mesa.so which implements interface Platform with its own MesaPlatform class. Your application knows nothing about "mesa" and contains no symbols with "mesa" in their name. Everything named "mesa" lives in mesa.so.
  Now you load your plugin "mesa.so" and it returns a UniqueModulePtr<MesaPlatform>. Because the only part of your project that knows the word "mesa" is mesa.so, the UniqueModulePtr<MesaPlatform> is fully and only instantiated inside mesa.so.
  Now you unload your plugin and three things get torn down, in this order:
   1. ~MesaPlatform inside mesa.so
   2. Unload/unmap mesa.so from memory.
   3. ~UniqueModulePtr<MesaPlatform> which was inside mesa.so, but is no longer loaded so you crash.

Yes, in theory you could avoid the crash, but only by either:
  (a) Leaking mesa.so by any means, accidental or intentional; or
  (b) Ensuring UniqueModulePtr is only used on the interface: UniqueModulePtr<Platform>.

I think (b) won't work right now without more fixes because UniqueModulePtr relies on being given the implementation class in order to figure out which shared library to keep resident. And (a) I know we have done both accidentally and intentionally, so did not notice the problem for a while.

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

So in summary, this proposal is the only solution that presently works.

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

Also, lp:~andreas-pokorny/mir/fix-1528135 does not solve the problem. Just works around it sufficiently to declare bug 1528135 fixed. But that doesn't help us at all here.

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

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

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

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

PASSED: Continuous integration, rev:3253
http://jenkins.qa.ubuntu.com/job/mir-ci/6013/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-android-vivid-i386-build/5528
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-clang-vivid-amd64-build/4435
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-vivid-touch/5484
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-xenial-touch/274
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/337
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-amd64-ci/337/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/337
        deb: http://jenkins.qa.ubuntu.com/job/mir-xenial-i386-ci/337/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5481
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-vivid-armhf/5481/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-touch/7952
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26636
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/270
        deb: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-builder-xenial-armhf/270/artifact/work/output/*zip*/output.zip
    SUCCESS: http://jenkins.qa.ubuntu.com/job/mir-mediumtests-runner-xenial-touch/128
    SUCCESS: http://s-jenkins.ubuntu-ci:8080/job/touch-flash-device/26639

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

review: Approve (continuous-integration)
Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :
Download full text (3.7 KiB)

Oh I am mostly arguing against your claim that UniqueModulePtr is unsafe. I havent looked into this bug properly...

> Returning UniqueModulePtr from your factory ABI within a plugin theoretically
> sounds like an improvement but requires a few pieces to be moved around and
> I'm not yet convinced it's sufficiently safe.

No as stated above this is the way it is meant to be used. And you might have noticed that already use it on the server side for that. And a closer look will show you that bug: #1528135 only exists because UniqueModulePtr allowed us to remove global variables that kept plugins alive.

> Forgive me for repeating but I want to restate the problem more concisely for
> other readers' and my own benefit:
>
> Let's imagine you have a plugin named mesa.so which implements interface
> Platform with its own MesaPlatform class. Your application knows nothing about
> "mesa" and contains no symbols with "mesa" in their name. Everything named
> "mesa" lives in mesa.so.
> Now you load your plugin "mesa.so" and it returns a
> UniqueModulePtr<MesaPlatform>. Because the only part of your project that
> knows the word "mesa" is mesa.so, the UniqueModulePtr<MesaPlatform> is fully
> and only instantiated inside mesa.so.
> Now you unload your plugin and three things get torn down, in this order:
> 1. ~MesaPlatform inside mesa.so
> 2. Unload/unmap mesa.so from memory.
> 3. ~UniqueModulePtr<MesaPlatform> which was inside mesa.so, but is no
> longer loaded so you crash.

The starting idea "Because the only part of your project that
knows the word "mesa" is mesa.so, the UniqueModulePtr<MesaPlatform> is fully
and only instantiated inside mesa.so." is already wrong.

Instead it should read. "A part of mirclient needs to create the ClientPlatfrom to be used, So it loads the right plugin and resolved create_client_platform and stores the returned UniqueModulePtr somewhere, then forgets about the mir::SharedLibrary it needed for that".
Then exactly 3 is not happening; the destructor call to the mir::client::ClientPlatform is encoded where the destructor call of the unique_ptr is encoded. The Deleter of that UniqueModulePtr will ensure that the SharedLibrary outlives the ClientPlatform object. Even when someone stores another UniqueModulePtr inside an object 'owned' by mesa.so (==ClientPlatform) wouldnt matter since someone still owns mesa.so(==ClientPlatform) so keeps it alive even when the object created and stored inside for whatever reason goes away. The important part here is that the plugin lifetime is now tied to the object lifetime created form it.

If for some reason the ClientPlatform creates objects for others to own that may outlive the ClientPlatform instance those need to be UniqueModulePtr too. See mir::graphics::Platform. Which is kind of a hint that graphics::Platform might be two or three separate things actually.

>
> Yes, in theory you could avoid the crash, but only by either:
> (a) Leaking mesa.so by any means, accidental or intentional; or
> (b) Ensuring UniqueModulePtr is only used on the interface:
> UniqueModulePtr<Platform>.
>
> I think (b) won't work right now without more fixes because UniqueModulePtr
> re...

Read more...

Revision history for this message
Andreas Pokorny (andreas-pokorny) wrote :
Download full text (3.7 KiB)

Oh I am mostly arguing against your claim that UniqueModulePtr is unsafe. I havent looked into this bug properly...

> Returning UniqueModulePtr from your factory ABI within a plugin theoretically
> sounds like an improvement but requires a few pieces to be moved around and
> I'm not yet convinced it's sufficiently safe.

No as stated above this is the way it is meant to be used. And you might have noticed that already use it on the server side for that. And a closer look will show you that bug: #1528135 only exists because UniqueModulePtr allowed us to remove global variables that kept plugins alive.

> Forgive me for repeating but I want to restate the problem more concisely for
> other readers' and my own benefit:
>
> Let's imagine you have a plugin named mesa.so which implements interface
> Platform with its own MesaPlatform class. Your application knows nothing about
> "mesa" and contains no symbols with "mesa" in their name. Everything named
> "mesa" lives in mesa.so.
> Now you load your plugin "mesa.so" and it returns a
> UniqueModulePtr<MesaPlatform>. Because the only part of your project that
> knows the word "mesa" is mesa.so, the UniqueModulePtr<MesaPlatform> is fully
> and only instantiated inside mesa.so.
> Now you unload your plugin and three things get torn down, in this order:
> 1. ~MesaPlatform inside mesa.so
> 2. Unload/unmap mesa.so from memory.
> 3. ~UniqueModulePtr<MesaPlatform> which was inside mesa.so, but is no
> longer loaded so you crash.

The starting idea "Because the only part of your project that
knows the word "mesa" is mesa.so, the UniqueModulePtr<MesaPlatform> is fully
and only instantiated inside mesa.so." is already wrong.

Instead it should read. "A part of mirclient needs to create the ClientPlatfrom to be used, So it loads the right plugin and resolved create_client_platform and stores the returned UniqueModulePtr somewhere, then forgets about the mir::SharedLibrary it needed for that".
Then exactly 3 is not happening; the destructor call to the mir::client::ClientPlatform is encoded where the destructor call of the unique_ptr is encoded. The Deleter of that UniqueModulePtr will ensure that the SharedLibrary outlives the ClientPlatform object. Even when someone stores another UniqueModulePtr inside an object 'owned' by mesa.so (==ClientPlatform) wouldnt matter since someone still owns mesa.so(==ClientPlatform) so keeps it alive even when the object created and stored inside for whatever reason goes away. The important part here is that the plugin lifetime is now tied to the object lifetime created form it.

If for some reason the ClientPlatform creates objects for others to own that may outlive the ClientPlatform instance those need to be UniqueModulePtr too. See mir::graphics::Platform. Which is kind of a hint that graphics::Platform might be two or three separate things actually.

>
> Yes, in theory you could avoid the crash, but only by either:
> (a) Leaking mesa.so by any means, accidental or intentional; or
> (b) Ensuring UniqueModulePtr is only used on the interface:
> UniqueModulePtr<Platform>.
>
> I think (b) won't work right now without more fixes because UniqueModulePtr
> re...

Read more...

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

I'm overwhelmed by the discussion about UniqueModulePtr - can someone tell me when a conclusion is reached and what it is?

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

I don't quite understand your argument, but just noticed my explanation was not sufficient.

What is happening is:
  1. We enter ~UniqueModulePtr<MesaPlatform>
  2. It destructs MesaPlatform
  3. It unloads mesa.so
  4. Somewhere near the end of ~UniqueModulePtr<MesaPlatform> ... crash! because that destructor was unmapped from memory but we are still trying to execute its instructions near the end of ~UniqueModulePtr<MesaPlatform>.

So UniqueModulePtr does crash once you fix all your leaks of mesa.so. The only way to avoid the crash is to hold an extra reference to the SharedLibrary, but that means UniqueModulePtr and make_module_ptr are pointless.

UniqueModulePtr seems like an elegant idea at first. But we all forgot that the template instance UniqueModulePtr<MesaPlatform> has its code inside mesa.so, while simultaneously needing to live longer than mesa.so is loaded. Once you fix your leaks, UniqueModulePtr will always crash.

Revision history for this message
Chris Halse Rogers (raof) wrote :

Ok. UniqueModulePtr appears to work as expected, as tested by unit-tests/test_module_deleter.cpp.

If UniqueModulePtr is unsafe, why don't these tests detect it?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Chris Halse Rogers (raof) wrote :

The outcome of some extreme debugging is that UniqueModulePtr works as designed, as long as ~RefCountedLibrary() is *not* defined in the module being unloaded (as expected).

I guess the answer is: it should be possible to use make_module_ptr, as long as everything created from the platform does so.

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/client/default_connection_configuration.cpp'
2--- src/client/default_connection_configuration.cpp 2015-12-23 08:13:34 +0000
3+++ src/client/default_connection_configuration.cpp 2016-01-19 00:24:38 +0000
4@@ -34,11 +34,9 @@
5 #include "lttng/shared_library_prober_report.h"
6 #include "connection_surface_map.h"
7 #include "lifecycle_control.h"
8-#include "mir/shared_library.h"
9 #include "mir/client_platform_factory.h"
10 #include "probing_client_platform_factory.h"
11 #include "mir_event_distributor.h"
12-#include "mir/shared_library_prober.h"
13
14 namespace mcl = mir::client;
15
16@@ -47,10 +45,6 @@
17 std::string const off_opt_val{"off"};
18 std::string const log_opt_val{"log"};
19 std::string const lttng_opt_val{"lttng"};
20-
21-// Shove this here until we properly manage the lifetime of our
22-// loadable modules
23-std::shared_ptr<mcl::ProbingClientPlatformFactory> the_platform_prober;
24 }
25
26 mcl::DefaultConnectionConfiguration::DefaultConnectionConfiguration(
27@@ -95,22 +89,20 @@
28 return client_platform_factory(
29 [this]
30 {
31- auto const platform_override = getenv("MIR_CLIENT_PLATFORM_LIB");
32- std::vector<std::shared_ptr<mir::SharedLibrary>> platform_plugins;
33- if (platform_override)
34- {
35- platform_plugins.push_back(std::make_shared<mir::SharedLibrary>(platform_override));
36- }
37+ std::vector<std::string> libs, paths;
38+ if (auto const lib = getenv("MIR_CLIENT_PLATFORM_LIB"))
39+ libs.push_back(lib);
40+
41+ if (auto path = getenv("MIR_CLIENT_PLATFORM_PATH"))
42+ paths.push_back(path);
43 else
44- {
45- auto const platform_path_override = getenv("MIR_CLIENT_PLATFORM_PATH");
46- auto const platform_path = platform_path_override ? platform_path_override : MIR_CLIENT_PLATFORM_PATH;
47- platform_plugins = mir::libraries_for_path(platform_path, *the_shared_library_prober_report());
48- }
49-
50- the_platform_prober = std::make_shared<mcl::ProbingClientPlatformFactory>(platform_plugins);
51-
52- return the_platform_prober;
53+ paths.push_back(MIR_CLIENT_PLATFORM_PATH);
54+
55+ return std::make_shared<mcl::ProbingClientPlatformFactory>(
56+ the_shared_library_prober_report(),
57+ libs,
58+ paths
59+ );
60 });
61 }
62
63
64=== modified file 'src/client/probing_client_platform_factory.cpp'
65--- src/client/probing_client_platform_factory.cpp 2016-01-05 10:23:12 +0000
66+++ src/client/probing_client_platform_factory.cpp 2016-01-19 00:24:38 +0000
67@@ -1,23 +1,60 @@
68 #include "probing_client_platform_factory.h"
69+#include "mir/client_platform.h"
70+#include "mir/shared_library.h"
71+#include "mir/shared_library_prober.h"
72+#include "mir/shared_library_prober_report.h"
73
74 #include <boost/exception/all.hpp>
75 #include <stdexcept>
76
77 namespace mcl = mir::client;
78
79-mcl::ProbingClientPlatformFactory::ProbingClientPlatformFactory(std::vector<std::shared_ptr<mir::SharedLibrary>> const& modules)
80- : platform_modules{modules}
81+mcl::ProbingClientPlatformFactory::ProbingClientPlatformFactory(
82+ std::shared_ptr<mir::SharedLibraryProberReport> const& rep,
83+ StringList const& force_libs,
84+ StringList const& lib_paths)
85+ : shared_library_prober_report{rep},
86+ platform_overrides{force_libs},
87+ platform_paths{lib_paths}
88 {
89- if (modules.empty())
90+ if (platform_overrides.empty() && platform_paths.empty())
91 {
92 BOOST_THROW_EXCEPTION(
93- std::runtime_error{"Attempted to create a ClientPlatformFactory with no platform modules"});
94+ std::runtime_error{"Attempted to create a ClientPlatformFactory with no platform paths or overrides"});
95 }
96 }
97
98 std::shared_ptr<mcl::ClientPlatform>
99 mcl::ProbingClientPlatformFactory::create_client_platform(mcl::ClientContext* context)
100 {
101+ // Note we don't want to keep unused platform modules loaded any longer
102+ // than it takes to choose the right one. So this list is local:
103+ std::vector<std::shared_ptr<mir::SharedLibrary>> platform_modules;
104+
105+ if (!platform_overrides.empty())
106+ {
107+ // Even forcing a choice, platform is only loaded on demand. It's good
108+ // to not need to hold the module open when there are no connections.
109+ // Also, this allows you to swap driver binaries on disk at run-time,
110+ // if you really wanted to.
111+
112+ for (auto const& platform : platform_overrides)
113+ platform_modules.push_back(
114+ std::make_shared<mir::SharedLibrary>(platform));
115+ }
116+ else
117+ {
118+ for (auto const& path : platform_paths)
119+ {
120+ // This is sub-optimal. We shouldn't need to have all drivers
121+ // loaded simultaneously, but we do for now due to this API:
122+ auto modules = mir::libraries_for_path(path,
123+ *shared_library_prober_report);
124+ for (auto const& module : modules)
125+ platform_modules.push_back(module);
126+ }
127+ }
128+
129 for (auto& module : platform_modules)
130 {
131 try
132
133=== modified file 'src/client/probing_client_platform_factory.h'
134--- src/client/probing_client_platform_factory.h 2015-01-21 07:34:50 +0000
135+++ src/client/probing_client_platform_factory.h 2016-01-19 00:24:38 +0000
136@@ -8,16 +8,25 @@
137
138 namespace mir
139 {
140+class SharedLibraryProberReport;
141+
142 namespace client
143 {
144 class ProbingClientPlatformFactory : public ClientPlatformFactory
145 {
146 public:
147- ProbingClientPlatformFactory(std::vector<std::shared_ptr<SharedLibrary>> const& modules);
148+ using StringList = std::vector<std::string>;
149+ ProbingClientPlatformFactory(
150+ std::shared_ptr<mir::SharedLibraryProberReport> const& rep,
151+ StringList const& force_libs,
152+ StringList const& lib_paths);
153
154 std::shared_ptr<ClientPlatform> create_client_platform(ClientContext *context) override;
155+
156 private:
157- std::vector<std::shared_ptr<SharedLibrary>> platform_modules;
158+ std::shared_ptr<mir::SharedLibraryProberReport> const shared_library_prober_report;
159+ StringList const platform_overrides;
160+ StringList const platform_paths;
161 };
162
163 }
164
165=== modified file 'src/platforms/mesa/client/client_platform_factory.cpp'
166--- src/platforms/mesa/client/client_platform_factory.cpp 2015-11-25 20:26:59 +0000
167+++ src/platforms/mesa/client/client_platform_factory.cpp 2016-01-19 00:24:38 +0000
168@@ -23,6 +23,7 @@
169 #include "buffer_file_ops.h"
170 #include "mir/egl_native_display_container.h"
171 #include "mir/assert_module_entry_point.h"
172+#include "mir/module_deleter.h"
173
174 #include <sys/mman.h>
175 #include <unistd.h>
176@@ -35,14 +36,18 @@
177
178 namespace
179 {
180-// Hack around the way mesa loads mir: This hack makes the
181-// necessary symbols global.
182+// Re-export our own symbols from mesa.so.N globally so that Mesa itself can
183+// find them with a simple dlsym(NULL,)
184 void ensure_loaded_with_rtld_global_mesa_client()
185 {
186 Dl_info info;
187
188 dladdr(reinterpret_cast<void*>(&ensure_loaded_with_rtld_global_mesa_client), &info);
189- dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
190+ void* reexport_self_global =
191+ dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
192+ // Yes, RTLD_NOLOAD does increase the ref count. So dlclose...
193+ if (reexport_self_global)
194+ dlclose(reexport_self_global);
195 }
196
197 struct RealBufferFileOps : public mclm::BufferFileOps
198@@ -83,7 +88,7 @@
199 BOOST_THROW_EXCEPTION((std::runtime_error{"Attempted to create Mesa client platform on non-Mesa server"}));
200 }
201 auto buffer_file_ops = std::make_shared<RealBufferFileOps>();
202- return std::make_shared<mclm::ClientPlatform>(
203+ return mir::make_module_ptr<mclm::ClientPlatform>(
204 context, buffer_file_ops, mcl::EGLNativeDisplayContainer::instance());
205 }
206
207
208=== modified file 'tests/mir_test_framework/CMakeLists.txt'
209--- tests/mir_test_framework/CMakeLists.txt 2016-01-05 19:14:36 +0000
210+++ tests/mir_test_framework/CMakeLists.txt 2016-01-19 00:24:38 +0000
211@@ -93,6 +93,7 @@
212 mirclientplatformstub
213
214 mir-test-framework-static
215+ mircommon
216 ${UMOCKDEV_LDFLAGS} ${UMOCKDEV_LIBRARIES}
217 )
218
219
220=== modified file 'tests/unit-tests/client/test_probing_client_platform_factory.cpp'
221--- tests/unit-tests/client/test_probing_client_platform_factory.cpp 2015-09-30 19:34:21 +0000
222+++ tests/unit-tests/client/test_probing_client_platform_factory.cpp 2016-01-19 00:24:38 +0000
223@@ -18,6 +18,7 @@
224
225 #include "mir/client_platform.h"
226 #include "src/client/probing_client_platform_factory.h"
227+#include "src/server/report/null_report_factory.h"
228
229 #include "mir/test/doubles/mock_client_context.h"
230 #include "mir_test_framework/executable_path.h"
231@@ -25,30 +26,55 @@
232
233 #include <gmock/gmock.h>
234 #include <gtest/gtest.h>
235+#include <dlfcn.h>
236
237 namespace mtf = mir_test_framework;
238 namespace mtd = mir::test::doubles;
239
240 namespace
241 {
242-std::vector<std::shared_ptr<mir::SharedLibrary>>
243+std::vector<std::string>
244 all_available_modules()
245 {
246- std::vector<std::shared_ptr<mir::SharedLibrary>> modules;
247+ std::vector<std::string> modules;
248 #if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)
249- modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("mesa")));
250+ modules.push_back(mtf::client_platform("mesa"));
251 #endif
252 #ifdef MIR_BUILD_PLATFORM_ANDROID
253- modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("android")));
254+ modules.push_back(mtf::client_platform("android"));
255 #endif
256 return modules;
257 }
258+
259+bool loaded(std::string const& path)
260+{
261+ void* x = dlopen(path.c_str(), RTLD_LAZY | RTLD_NOLOAD);
262+ if (x)
263+ dlclose(x);
264+ return !!x;
265+}
266+
267+void populate_valid(MirPlatformPackage& pkg)
268+{
269+ memset(&pkg, 0, sizeof(MirPlatformPackage));
270+ pkg.fd_items = 1;
271+ pkg.fd[0] = 23;
272+}
273+
274+void safely_unload(std::shared_ptr<mir::client::ClientPlatform>& platform)
275+{
276+ ASSERT_TRUE(platform.unique());
277+ platform.reset();
278+}
279+
280 }
281
282 TEST(ProbingClientPlatformFactory, ThrowsErrorWhenConstructedWithNoPlatforms)
283 {
284 std::vector<std::shared_ptr<mir::SharedLibrary>> empty_modules;
285- EXPECT_THROW(mir::client::ProbingClientPlatformFactory{empty_modules},
286+ EXPECT_THROW(mir::client::ProbingClientPlatformFactory(
287+ mir::report::null_shared_library_prober_report(),
288+ {}, {}),
289 std::runtime_error);
290 }
291
292@@ -56,7 +82,10 @@
293 {
294 using namespace testing;
295
296- mir::client::ProbingClientPlatformFactory factory{all_available_modules()};
297+ mir::client::ProbingClientPlatformFactory factory(
298+ mir::report::null_shared_library_prober_report(),
299+ all_available_modules(),
300+ {});
301
302 mtd::MockClientContext context;
303 ON_CALL(context, populate_server_package(_))
304@@ -73,6 +102,69 @@
305 std::runtime_error);
306 }
307
308+TEST(ProbingClientPlatformFactory, DoesNotLeakTheUsedDriverModuleOnShutdown)
309+{ // Regression test for LP: #1527449
310+ using namespace testing;
311+ auto const modules = all_available_modules();
312+ ASSERT_FALSE(modules.empty());
313+ std::string const preferred_module = modules.front();
314+
315+ mir::client::ProbingClientPlatformFactory factory(
316+ mir::report::null_shared_library_prober_report(),
317+ {preferred_module},
318+ {});
319+
320+ std::shared_ptr<mir::client::ClientPlatform> platform;
321+ mtd::MockClientContext context;
322+ ON_CALL(context, populate_server_package(_))
323+ .WillByDefault(Invoke(populate_valid));
324+
325+ ASSERT_FALSE(loaded(preferred_module));
326+ platform = factory.create_client_platform(&context);
327+ ASSERT_TRUE(loaded(preferred_module));
328+ safely_unload(platform);
329+ EXPECT_FALSE(loaded(preferred_module));
330+}
331+
332+TEST(ProbingClientPlatformFactory, DoesNotLeakUnusedDriverModulesOnStartup)
333+{ // Regression test for LP: #1527449 and LP: #1526658
334+ using namespace testing;
335+ auto const modules = all_available_modules();
336+ ASSERT_FALSE(modules.empty());
337+
338+ // Note: This test is only really effective with nmodules>1, which many of
339+ // our builds will have. But nmodules==1 is harmless.
340+
341+ mir::client::ProbingClientPlatformFactory factory(
342+ mir::report::null_shared_library_prober_report(),
343+ modules,
344+ {});
345+
346+ std::shared_ptr<mir::client::ClientPlatform> platform;
347+ mtd::MockClientContext context;
348+ ON_CALL(context, populate_server_package(_))
349+ .WillByDefault(Invoke(populate_valid));
350+
351+ int nloaded = 0;
352+ for (auto const& m : modules)
353+ if (loaded(m)) ++nloaded;
354+ ASSERT_EQ(0, nloaded);
355+
356+ platform = factory.create_client_platform(&context);
357+
358+ nloaded = 0;
359+ for (auto const& m : modules)
360+ if (loaded(m)) ++nloaded;
361+ EXPECT_EQ(1, nloaded); // expect not assert, because we need safely_unload
362+
363+ safely_unload(platform);
364+
365+ nloaded = 0;
366+ for (auto const& m : modules)
367+ if (loaded(m)) ++nloaded;
368+ ASSERT_EQ(0, nloaded);
369+}
370+
371 #if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)
372 TEST(ProbingClientPlatformFactory, CreatesMesaPlatformWhenAppropriate)
373 #else
374@@ -81,7 +173,10 @@
375 {
376 using namespace testing;
377
378- mir::client::ProbingClientPlatformFactory factory{all_available_modules()};
379+ mir::client::ProbingClientPlatformFactory factory(
380+ mir::report::null_shared_library_prober_report(),
381+ all_available_modules(),
382+ {});
383
384 mtd::MockClientContext context;
385 ON_CALL(context, populate_server_package(_))
386@@ -95,6 +190,7 @@
387 }));
388 auto platform = factory.create_client_platform(&context);
389 EXPECT_EQ(mir_platform_type_gbm, platform->platform_type());
390+ safely_unload(platform);
391 }
392
393 #ifdef MIR_BUILD_PLATFORM_ANDROID
394@@ -105,7 +201,10 @@
395 {
396 using namespace testing;
397
398- mir::client::ProbingClientPlatformFactory factory{all_available_modules()};
399+ mir::client::ProbingClientPlatformFactory factory(
400+ mir::report::null_shared_library_prober_report(),
401+ all_available_modules(),
402+ {});
403
404 mtd::MockClientContext context;
405 ON_CALL(context, populate_server_package(_))
406@@ -118,6 +217,7 @@
407
408 auto platform = factory.create_client_platform(&context);
409 EXPECT_EQ(mir_platform_type_android, platform->platform_type());
410+ safely_unload(platform);
411 }
412
413 TEST(ProbingClientPlatformFactory, IgnoresNonClientPlatformModules)
414@@ -126,10 +226,13 @@
415
416 auto modules = all_available_modules();
417 // NOTE: For minimum fuss, load something that has minimal side-effects...
418- modules.push_back(std::make_shared<mir::SharedLibrary>("libc.so.6"));
419- modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("dummy.so")));
420+ modules.push_back("libc.so.6");
421+ modules.push_back(mtf::client_platform("dummy.so"));
422
423- mir::client::ProbingClientPlatformFactory factory{modules};
424+ mir::client::ProbingClientPlatformFactory factory(
425+ mir::report::null_shared_library_prober_report(),
426+ modules,
427+ {});
428
429 mtd::MockClientContext context;
430 ON_CALL(context, populate_server_package(_))
431@@ -139,4 +242,5 @@
432 }));
433
434 auto platform = factory.create_client_platform(&context);
435+ safely_unload(platform);
436 }

Subscribers

People subscribed via source and target branches