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
=== modified file 'src/client/default_connection_configuration.cpp'
--- src/client/default_connection_configuration.cpp 2015-12-23 08:13:34 +0000
+++ src/client/default_connection_configuration.cpp 2016-01-19 00:24:38 +0000
@@ -34,11 +34,9 @@
34#include "lttng/shared_library_prober_report.h"34#include "lttng/shared_library_prober_report.h"
35#include "connection_surface_map.h"35#include "connection_surface_map.h"
36#include "lifecycle_control.h"36#include "lifecycle_control.h"
37#include "mir/shared_library.h"
38#include "mir/client_platform_factory.h"37#include "mir/client_platform_factory.h"
39#include "probing_client_platform_factory.h"38#include "probing_client_platform_factory.h"
40#include "mir_event_distributor.h"39#include "mir_event_distributor.h"
41#include "mir/shared_library_prober.h"
4240
43namespace mcl = mir::client;41namespace mcl = mir::client;
4442
@@ -47,10 +45,6 @@
47std::string const off_opt_val{"off"};45std::string const off_opt_val{"off"};
48std::string const log_opt_val{"log"};46std::string const log_opt_val{"log"};
49std::string const lttng_opt_val{"lttng"};47std::string const lttng_opt_val{"lttng"};
50
51// Shove this here until we properly manage the lifetime of our
52// loadable modules
53std::shared_ptr<mcl::ProbingClientPlatformFactory> the_platform_prober;
54}48}
5549
56mcl::DefaultConnectionConfiguration::DefaultConnectionConfiguration(50mcl::DefaultConnectionConfiguration::DefaultConnectionConfiguration(
@@ -95,22 +89,20 @@
95 return client_platform_factory(89 return client_platform_factory(
96 [this]90 [this]
97 {91 {
98 auto const platform_override = getenv("MIR_CLIENT_PLATFORM_LIB");92 std::vector<std::string> libs, paths;
99 std::vector<std::shared_ptr<mir::SharedLibrary>> platform_plugins;93 if (auto const lib = getenv("MIR_CLIENT_PLATFORM_LIB"))
100 if (platform_override)94 libs.push_back(lib);
101 {95
102 platform_plugins.push_back(std::make_shared<mir::SharedLibrary>(platform_override));96 if (auto path = getenv("MIR_CLIENT_PLATFORM_PATH"))
103 }97 paths.push_back(path);
104 else98 else
105 {99 paths.push_back(MIR_CLIENT_PLATFORM_PATH);
106 auto const platform_path_override = getenv("MIR_CLIENT_PLATFORM_PATH");100
107 auto const platform_path = platform_path_override ? platform_path_override : MIR_CLIENT_PLATFORM_PATH;101 return std::make_shared<mcl::ProbingClientPlatformFactory>(
108 platform_plugins = mir::libraries_for_path(platform_path, *the_shared_library_prober_report());102 the_shared_library_prober_report(),
109 }103 libs,
110104 paths
111 the_platform_prober = std::make_shared<mcl::ProbingClientPlatformFactory>(platform_plugins);105 );
112
113 return the_platform_prober;
114 });106 });
115}107}
116108
117109
=== modified file 'src/client/probing_client_platform_factory.cpp'
--- src/client/probing_client_platform_factory.cpp 2016-01-05 10:23:12 +0000
+++ src/client/probing_client_platform_factory.cpp 2016-01-19 00:24:38 +0000
@@ -1,23 +1,60 @@
1#include "probing_client_platform_factory.h"1#include "probing_client_platform_factory.h"
2#include "mir/client_platform.h"
3#include "mir/shared_library.h"
4#include "mir/shared_library_prober.h"
5#include "mir/shared_library_prober_report.h"
26
3#include <boost/exception/all.hpp>7#include <boost/exception/all.hpp>
4#include <stdexcept>8#include <stdexcept>
59
6namespace mcl = mir::client;10namespace mcl = mir::client;
711
8mcl::ProbingClientPlatformFactory::ProbingClientPlatformFactory(std::vector<std::shared_ptr<mir::SharedLibrary>> const& modules)12mcl::ProbingClientPlatformFactory::ProbingClientPlatformFactory(
9 : platform_modules{modules}13 std::shared_ptr<mir::SharedLibraryProberReport> const& rep,
14 StringList const& force_libs,
15 StringList const& lib_paths)
16 : shared_library_prober_report{rep},
17 platform_overrides{force_libs},
18 platform_paths{lib_paths}
10{19{
11 if (modules.empty())20 if (platform_overrides.empty() && platform_paths.empty())
12 {21 {
13 BOOST_THROW_EXCEPTION(22 BOOST_THROW_EXCEPTION(
14 std::runtime_error{"Attempted to create a ClientPlatformFactory with no platform modules"});23 std::runtime_error{"Attempted to create a ClientPlatformFactory with no platform paths or overrides"});
15 }24 }
16}25}
1726
18std::shared_ptr<mcl::ClientPlatform>27std::shared_ptr<mcl::ClientPlatform>
19mcl::ProbingClientPlatformFactory::create_client_platform(mcl::ClientContext* context)28mcl::ProbingClientPlatformFactory::create_client_platform(mcl::ClientContext* context)
20{29{
30 // Note we don't want to keep unused platform modules loaded any longer
31 // than it takes to choose the right one. So this list is local:
32 std::vector<std::shared_ptr<mir::SharedLibrary>> platform_modules;
33
34 if (!platform_overrides.empty())
35 {
36 // Even forcing a choice, platform is only loaded on demand. It's good
37 // to not need to hold the module open when there are no connections.
38 // Also, this allows you to swap driver binaries on disk at run-time,
39 // if you really wanted to.
40
41 for (auto const& platform : platform_overrides)
42 platform_modules.push_back(
43 std::make_shared<mir::SharedLibrary>(platform));
44 }
45 else
46 {
47 for (auto const& path : platform_paths)
48 {
49 // This is sub-optimal. We shouldn't need to have all drivers
50 // loaded simultaneously, but we do for now due to this API:
51 auto modules = mir::libraries_for_path(path,
52 *shared_library_prober_report);
53 for (auto const& module : modules)
54 platform_modules.push_back(module);
55 }
56 }
57
21 for (auto& module : platform_modules)58 for (auto& module : platform_modules)
22 {59 {
23 try60 try
2461
=== modified file 'src/client/probing_client_platform_factory.h'
--- src/client/probing_client_platform_factory.h 2015-01-21 07:34:50 +0000
+++ src/client/probing_client_platform_factory.h 2016-01-19 00:24:38 +0000
@@ -8,16 +8,25 @@
88
9namespace mir9namespace mir
10{10{
11class SharedLibraryProberReport;
12
11namespace client13namespace client
12{14{
13class ProbingClientPlatformFactory : public ClientPlatformFactory15class ProbingClientPlatformFactory : public ClientPlatformFactory
14{16{
15public:17public:
16 ProbingClientPlatformFactory(std::vector<std::shared_ptr<SharedLibrary>> const& modules);18 using StringList = std::vector<std::string>;
19 ProbingClientPlatformFactory(
20 std::shared_ptr<mir::SharedLibraryProberReport> const& rep,
21 StringList const& force_libs,
22 StringList const& lib_paths);
1723
18 std::shared_ptr<ClientPlatform> create_client_platform(ClientContext *context) override;24 std::shared_ptr<ClientPlatform> create_client_platform(ClientContext *context) override;
25
19private:26private:
20 std::vector<std::shared_ptr<SharedLibrary>> platform_modules;27 std::shared_ptr<mir::SharedLibraryProberReport> const shared_library_prober_report;
28 StringList const platform_overrides;
29 StringList const platform_paths;
21};30};
2231
23}32}
2433
=== modified file 'src/platforms/mesa/client/client_platform_factory.cpp'
--- src/platforms/mesa/client/client_platform_factory.cpp 2015-11-25 20:26:59 +0000
+++ src/platforms/mesa/client/client_platform_factory.cpp 2016-01-19 00:24:38 +0000
@@ -23,6 +23,7 @@
23#include "buffer_file_ops.h"23#include "buffer_file_ops.h"
24#include "mir/egl_native_display_container.h"24#include "mir/egl_native_display_container.h"
25#include "mir/assert_module_entry_point.h"25#include "mir/assert_module_entry_point.h"
26#include "mir/module_deleter.h"
2627
27#include <sys/mman.h>28#include <sys/mman.h>
28#include <unistd.h>29#include <unistd.h>
@@ -35,14 +36,18 @@
3536
36namespace37namespace
37{38{
38// Hack around the way mesa loads mir: This hack makes the39// Re-export our own symbols from mesa.so.N globally so that Mesa itself can
39// necessary symbols global.40// find them with a simple dlsym(NULL,)
40void ensure_loaded_with_rtld_global_mesa_client()41void ensure_loaded_with_rtld_global_mesa_client()
41{42{
42 Dl_info info;43 Dl_info info;
4344
44 dladdr(reinterpret_cast<void*>(&ensure_loaded_with_rtld_global_mesa_client), &info);45 dladdr(reinterpret_cast<void*>(&ensure_loaded_with_rtld_global_mesa_client), &info);
45 dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);46 void* reexport_self_global =
47 dlopen(info.dli_fname, RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
48 // Yes, RTLD_NOLOAD does increase the ref count. So dlclose...
49 if (reexport_self_global)
50 dlclose(reexport_self_global);
46}51}
4752
48struct RealBufferFileOps : public mclm::BufferFileOps53struct RealBufferFileOps : public mclm::BufferFileOps
@@ -83,7 +88,7 @@
83 BOOST_THROW_EXCEPTION((std::runtime_error{"Attempted to create Mesa client platform on non-Mesa server"}));88 BOOST_THROW_EXCEPTION((std::runtime_error{"Attempted to create Mesa client platform on non-Mesa server"}));
84 }89 }
85 auto buffer_file_ops = std::make_shared<RealBufferFileOps>();90 auto buffer_file_ops = std::make_shared<RealBufferFileOps>();
86 return std::make_shared<mclm::ClientPlatform>(91 return mir::make_module_ptr<mclm::ClientPlatform>(
87 context, buffer_file_ops, mcl::EGLNativeDisplayContainer::instance());92 context, buffer_file_ops, mcl::EGLNativeDisplayContainer::instance());
88}93}
8994
9095
=== modified file 'tests/mir_test_framework/CMakeLists.txt'
--- tests/mir_test_framework/CMakeLists.txt 2016-01-05 19:14:36 +0000
+++ tests/mir_test_framework/CMakeLists.txt 2016-01-19 00:24:38 +0000
@@ -93,6 +93,7 @@
93 mirclientplatformstub93 mirclientplatformstub
9494
95 mir-test-framework-static95 mir-test-framework-static
96 mircommon
96 ${UMOCKDEV_LDFLAGS} ${UMOCKDEV_LIBRARIES}97 ${UMOCKDEV_LDFLAGS} ${UMOCKDEV_LIBRARIES}
97)98)
9899
99100
=== modified file 'tests/unit-tests/client/test_probing_client_platform_factory.cpp'
--- tests/unit-tests/client/test_probing_client_platform_factory.cpp 2015-09-30 19:34:21 +0000
+++ tests/unit-tests/client/test_probing_client_platform_factory.cpp 2016-01-19 00:24:38 +0000
@@ -18,6 +18,7 @@
1818
19#include "mir/client_platform.h"19#include "mir/client_platform.h"
20#include "src/client/probing_client_platform_factory.h"20#include "src/client/probing_client_platform_factory.h"
21#include "src/server/report/null_report_factory.h"
2122
22#include "mir/test/doubles/mock_client_context.h"23#include "mir/test/doubles/mock_client_context.h"
23#include "mir_test_framework/executable_path.h"24#include "mir_test_framework/executable_path.h"
@@ -25,30 +26,55 @@
2526
26#include <gmock/gmock.h>27#include <gmock/gmock.h>
27#include <gtest/gtest.h>28#include <gtest/gtest.h>
29#include <dlfcn.h>
2830
29namespace mtf = mir_test_framework;31namespace mtf = mir_test_framework;
30namespace mtd = mir::test::doubles;32namespace mtd = mir::test::doubles;
3133
32namespace34namespace
33{35{
34std::vector<std::shared_ptr<mir::SharedLibrary>>36std::vector<std::string>
35all_available_modules()37all_available_modules()
36{38{
37 std::vector<std::shared_ptr<mir::SharedLibrary>> modules;39 std::vector<std::string> modules;
38#if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)40#if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)
39 modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("mesa")));41 modules.push_back(mtf::client_platform("mesa"));
40#endif42#endif
41#ifdef MIR_BUILD_PLATFORM_ANDROID43#ifdef MIR_BUILD_PLATFORM_ANDROID
42 modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("android")));44 modules.push_back(mtf::client_platform("android"));
43#endif45#endif
44 return modules;46 return modules;
45}47}
48
49bool loaded(std::string const& path)
50{
51 void* x = dlopen(path.c_str(), RTLD_LAZY | RTLD_NOLOAD);
52 if (x)
53 dlclose(x);
54 return !!x;
55}
56
57void populate_valid(MirPlatformPackage& pkg)
58{
59 memset(&pkg, 0, sizeof(MirPlatformPackage));
60 pkg.fd_items = 1;
61 pkg.fd[0] = 23;
62}
63
64void safely_unload(std::shared_ptr<mir::client::ClientPlatform>& platform)
65{
66 ASSERT_TRUE(platform.unique());
67 platform.reset();
68}
69
46}70}
4771
48TEST(ProbingClientPlatformFactory, ThrowsErrorWhenConstructedWithNoPlatforms)72TEST(ProbingClientPlatformFactory, ThrowsErrorWhenConstructedWithNoPlatforms)
49{73{
50 std::vector<std::shared_ptr<mir::SharedLibrary>> empty_modules;74 std::vector<std::shared_ptr<mir::SharedLibrary>> empty_modules;
51 EXPECT_THROW(mir::client::ProbingClientPlatformFactory{empty_modules},75 EXPECT_THROW(mir::client::ProbingClientPlatformFactory(
76 mir::report::null_shared_library_prober_report(),
77 {}, {}),
52 std::runtime_error);78 std::runtime_error);
53}79}
5480
@@ -56,7 +82,10 @@
56{82{
57 using namespace testing;83 using namespace testing;
5884
59 mir::client::ProbingClientPlatformFactory factory{all_available_modules()};85 mir::client::ProbingClientPlatformFactory factory(
86 mir::report::null_shared_library_prober_report(),
87 all_available_modules(),
88 {});
6089
61 mtd::MockClientContext context;90 mtd::MockClientContext context;
62 ON_CALL(context, populate_server_package(_))91 ON_CALL(context, populate_server_package(_))
@@ -73,6 +102,69 @@
73 std::runtime_error);102 std::runtime_error);
74}103}
75104
105TEST(ProbingClientPlatformFactory, DoesNotLeakTheUsedDriverModuleOnShutdown)
106{ // Regression test for LP: #1527449
107 using namespace testing;
108 auto const modules = all_available_modules();
109 ASSERT_FALSE(modules.empty());
110 std::string const preferred_module = modules.front();
111
112 mir::client::ProbingClientPlatformFactory factory(
113 mir::report::null_shared_library_prober_report(),
114 {preferred_module},
115 {});
116
117 std::shared_ptr<mir::client::ClientPlatform> platform;
118 mtd::MockClientContext context;
119 ON_CALL(context, populate_server_package(_))
120 .WillByDefault(Invoke(populate_valid));
121
122 ASSERT_FALSE(loaded(preferred_module));
123 platform = factory.create_client_platform(&context);
124 ASSERT_TRUE(loaded(preferred_module));
125 safely_unload(platform);
126 EXPECT_FALSE(loaded(preferred_module));
127}
128
129TEST(ProbingClientPlatformFactory, DoesNotLeakUnusedDriverModulesOnStartup)
130{ // Regression test for LP: #1527449 and LP: #1526658
131 using namespace testing;
132 auto const modules = all_available_modules();
133 ASSERT_FALSE(modules.empty());
134
135 // Note: This test is only really effective with nmodules>1, which many of
136 // our builds will have. But nmodules==1 is harmless.
137
138 mir::client::ProbingClientPlatformFactory factory(
139 mir::report::null_shared_library_prober_report(),
140 modules,
141 {});
142
143 std::shared_ptr<mir::client::ClientPlatform> platform;
144 mtd::MockClientContext context;
145 ON_CALL(context, populate_server_package(_))
146 .WillByDefault(Invoke(populate_valid));
147
148 int nloaded = 0;
149 for (auto const& m : modules)
150 if (loaded(m)) ++nloaded;
151 ASSERT_EQ(0, nloaded);
152
153 platform = factory.create_client_platform(&context);
154
155 nloaded = 0;
156 for (auto const& m : modules)
157 if (loaded(m)) ++nloaded;
158 EXPECT_EQ(1, nloaded); // expect not assert, because we need safely_unload
159
160 safely_unload(platform);
161
162 nloaded = 0;
163 for (auto const& m : modules)
164 if (loaded(m)) ++nloaded;
165 ASSERT_EQ(0, nloaded);
166}
167
76#if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)168#if defined(MIR_BUILD_PLATFORM_MESA_KMS) || defined(MIR_BUILD_PLATFORM_MESA_X11)
77TEST(ProbingClientPlatformFactory, CreatesMesaPlatformWhenAppropriate)169TEST(ProbingClientPlatformFactory, CreatesMesaPlatformWhenAppropriate)
78#else170#else
@@ -81,7 +173,10 @@
81{173{
82 using namespace testing;174 using namespace testing;
83175
84 mir::client::ProbingClientPlatformFactory factory{all_available_modules()};176 mir::client::ProbingClientPlatformFactory factory(
177 mir::report::null_shared_library_prober_report(),
178 all_available_modules(),
179 {});
85180
86 mtd::MockClientContext context;181 mtd::MockClientContext context;
87 ON_CALL(context, populate_server_package(_))182 ON_CALL(context, populate_server_package(_))
@@ -95,6 +190,7 @@
95 }));190 }));
96 auto platform = factory.create_client_platform(&context);191 auto platform = factory.create_client_platform(&context);
97 EXPECT_EQ(mir_platform_type_gbm, platform->platform_type());192 EXPECT_EQ(mir_platform_type_gbm, platform->platform_type());
193 safely_unload(platform);
98}194}
99195
100#ifdef MIR_BUILD_PLATFORM_ANDROID196#ifdef MIR_BUILD_PLATFORM_ANDROID
@@ -105,7 +201,10 @@
105{201{
106 using namespace testing;202 using namespace testing;
107203
108 mir::client::ProbingClientPlatformFactory factory{all_available_modules()};204 mir::client::ProbingClientPlatformFactory factory(
205 mir::report::null_shared_library_prober_report(),
206 all_available_modules(),
207 {});
109208
110 mtd::MockClientContext context;209 mtd::MockClientContext context;
111 ON_CALL(context, populate_server_package(_))210 ON_CALL(context, populate_server_package(_))
@@ -118,6 +217,7 @@
118217
119 auto platform = factory.create_client_platform(&context);218 auto platform = factory.create_client_platform(&context);
120 EXPECT_EQ(mir_platform_type_android, platform->platform_type());219 EXPECT_EQ(mir_platform_type_android, platform->platform_type());
220 safely_unload(platform);
121}221}
122222
123TEST(ProbingClientPlatformFactory, IgnoresNonClientPlatformModules)223TEST(ProbingClientPlatformFactory, IgnoresNonClientPlatformModules)
@@ -126,10 +226,13 @@
126226
127 auto modules = all_available_modules();227 auto modules = all_available_modules();
128 // NOTE: For minimum fuss, load something that has minimal side-effects...228 // NOTE: For minimum fuss, load something that has minimal side-effects...
129 modules.push_back(std::make_shared<mir::SharedLibrary>("libc.so.6"));229 modules.push_back("libc.so.6");
130 modules.push_back(std::make_shared<mir::SharedLibrary>(mtf::client_platform("dummy.so")));230 modules.push_back(mtf::client_platform("dummy.so"));
131231
132 mir::client::ProbingClientPlatformFactory factory{modules};232 mir::client::ProbingClientPlatformFactory factory(
233 mir::report::null_shared_library_prober_report(),
234 modules,
235 {});
133236
134 mtd::MockClientContext context;237 mtd::MockClientContext context;
135 ON_CALL(context, populate_server_package(_))238 ON_CALL(context, populate_server_package(_))
@@ -139,4 +242,5 @@
139 }));242 }));
140243
141 auto platform = factory.create_client_platform(&context);244 auto platform = factory.create_client_platform(&context);
245 safely_unload(platform);
142}246}

Subscribers

People subscribed via source and target branches