Merge lp:~vanvugt/mir/fix-1527449 into lp:mir
- fix-1527449
- Merge into development-branch
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 |
Related bugs: |
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_
Description of the change
.
PS Jenkins bot (ps-jenkins) wrote : | # |
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...
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:3249
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Mir CI Bot (mir-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:3249
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
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_
+ std::shared_
+private:
+ std::shared_
+};
This looks like an intrusive design to tackle the same problem addressed more elegantly by mir::make_
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
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.
Daniel van Vugt (vanvugt) wrote : | # |
OK, I switched to mir::make_
The reason is that the act of constructing a mesa platform from mesa.so.4 :
mir:
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.
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.
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
To make proper use of UniqueModulePtr you need to use it as the return type of the factory functions that get dlsymed.
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
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. ~UniqueModulePt
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
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.
Daniel van Vugt (vanvugt) wrote : | # |
So in summary, this proposal is the only solution that presently works.
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.
Mir CI Bot (mir-ci-bot) wrote : | # |
PASSED: Continuous integration, rev:3253
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:3253
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Andreas Pokorny (andreas-pokorny) wrote : | # |
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
> knows the word "mesa" is mesa.so, the UniqueModulePtr
> 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. ~UniqueModulePt
> 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
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_
Then exactly 3 is not happening; the destructor call to the mir::client:
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:
>
> 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
>
> I think (b) won't work right now without more fixes because UniqueModulePtr
> re...
Andreas Pokorny (andreas-pokorny) wrote : | # |
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
> knows the word "mesa" is mesa.so, the UniqueModulePtr
> 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. ~UniqueModulePt
> 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
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_
Then exactly 3 is not happening; the destructor call to the mir::client:
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:
>
> 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
>
> I think (b) won't work right now without more fixes because UniqueModulePtr
> re...
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?
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 ~UniqueModulePt
2. It destructs MesaPlatform
3. It unloads mesa.so
4. Somewhere near the end of ~UniqueModulePt
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
Chris Halse Rogers (raof) wrote : | # |
Ok. UniqueModulePtr appears to work as expected, as tested by unit-tests/
If UniqueModulePtr is unsafe, why don't these tests detect it?
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:3254
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Chris Halse Rogers (raof) wrote : | # |
The outcome of some extreme debugging is that UniqueModulePtr works as designed, as long as ~RefCountedLibr
I guess the answer is: it should be possible to use make_module_ptr, as long as everything created from the platform does so.
Preview Diff
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 | } |
FAILED: Continuous integration, rev:3246 jenkins. qa.ubuntu. com/job/ mir-ci/ 5955/ jenkins. qa.ubuntu. com/job/ mir-android- vivid-i386- build/5457 jenkins. qa.ubuntu. com/job/ mir-clang- vivid-amd64- build/4364 jenkins. qa.ubuntu. com/job/ mir-mediumtests -vivid- touch/5413/ console jenkins. qa.ubuntu. com/job/ mir-xenial- amd64-ci/ 279/console jenkins. qa.ubuntu. com/job/ mir-xenial- i386-ci/ 279/console jenkins. qa.ubuntu. com/job/ mir-mediumtests -builder- vivid-armhf/ 5410/console
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- ci/5955/ rebuild
http://