Skip to content

Commit c189b78

Browse files
azeeyscpeterschapulina
authored
Add option to use RTLD_NODELETE when loading a library (#102)
Signed-off-by: Addisu Z. Taddese <[email protected]> Signed-off-by: Louise Poubel <[email protected]> Co-authored-by: Steve Peters <[email protected]> Co-authored-by: Louise Poubel <[email protected]>
1 parent daa9a65 commit c189b78

9 files changed

+253
-5
lines changed

loader/include/gz/plugin/Loader.hh

+11
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,17 @@ namespace gz
133133
public: std::unordered_set<std::string> LoadLib(
134134
const std::string &_pathToLibrary);
135135

136+
/// \brief Load a library at the given path
137+
///
138+
/// \param[in] _pathToLibrary
139+
/// The path to a library
140+
/// \param[in] _noDelete
141+
/// If true, RTLD_NODELETE will be used when loading the library.
142+
///
143+
/// \returns The set of plugins that have been loaded from the library
144+
public: std::unordered_set<std::string> LoadLib(
145+
const std::string &_pathToLibrary, bool _noDelete);
146+
136147
/// \brief Instantiates a plugin for the given plugin name
137148
///
138149
/// \param[in] _pluginNameOrAlias

loader/src/Loader.cc

+17-4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ namespace gz
4646
/// \return If a library exists at the given path, get a point to its dl
4747
/// handle. If the library does not exist, get a nullptr.
4848
public: std::shared_ptr<void> LoadLib(
49-
const std::string &_pathToLibrary);
49+
const std::string &_pathToLibrary, bool _noDelete);
5050

5151
/// \brief Using a dl handle produced by LoadLib, extract the
5252
/// Info from the loaded library.
@@ -139,12 +139,18 @@ namespace gz
139139
/////////////////////////////////////////////////
140140
std::unordered_set<std::string> Loader::LoadLib(
141141
const std::string &_pathToLibrary)
142+
{
143+
return this->LoadLib(_pathToLibrary, false);
144+
}
145+
/////////////////////////////////////////////////
146+
std::unordered_set<std::string> Loader::LoadLib(
147+
const std::string &_pathToLibrary, bool _noDelete)
142148
{
143149
std::unordered_set<std::string> newPlugins;
144150

145151
// Attempt to load the library at this path
146152
const std::shared_ptr<void> &dlHandle =
147-
this->dataPtr->LoadLib(_pathToLibrary);
153+
this->dataPtr->LoadLib(_pathToLibrary, _noDelete);
148154

149155
// Quit early and return an empty set of plugin names if we did not
150156
// actually get a valid dlHandle.
@@ -420,7 +426,7 @@ namespace gz
420426

421427
/////////////////////////////////////////////////
422428
std::shared_ptr<void> Loader::Implementation::LoadLib(
423-
const std::string &_full_path)
429+
const std::string &_full_path, bool _noDelete)
424430
{
425431
std::shared_ptr<void> dlHandlePtr;
426432

@@ -431,7 +437,14 @@ namespace gz
431437

432438
// NOTE: We open using RTLD_LOCAL instead of RTLD_GLOBAL to prevent the
433439
// symbols of different libraries from writing over each other.
434-
void *dlHandle = dlopen(_full_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
440+
#ifdef _WIN32
441+
// RTLD_NODELETE is not defined in dlfcn-32.
442+
(void) _noDelete;
443+
int dlopenMode = RTLD_LAZY | RTLD_LOCAL;
444+
#else
445+
int dlopenMode = RTLD_LAZY | RTLD_LOCAL | (_noDelete ? RTLD_NODELETE : 0);
446+
#endif
447+
void *dlHandle = dlopen(_full_path.c_str(), dlopenMode);
435448

436449
const char *loadError = dlerror();
437450
if (nullptr == dlHandle || nullptr != loadError)

test/integration/CMakeLists.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ foreach(test ${test_targets})
2020
GzBadPluginSize
2121
GzDummyPlugins
2222
GzFactoryPlugins
23-
GzTemplatedPlugins)
23+
GzTemplatedPlugins
24+
GzInstanceCounter)
2425

2526
target_compile_definitions(${test} PRIVATE
2627
"${plugin_target}_LIB=\"$<TARGET_FILE:${plugin_target}>\"")
@@ -33,6 +34,8 @@ foreach(test
3334
INTEGRATION_EnablePluginFromThis_TEST
3435
INTEGRATION_factory
3536
INTEGRATION_plugin
37+
INTEGRATION_plugin_unload_with_nodelete
38+
INTEGRATION_plugin_unload_without_nodelete
3639
INTEGRATION_WeakPluginPtr)
3740

3841
if(TARGET ${test})

test/integration/plugin_unload.hh

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (C) 2022 Open Source Robotics Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
#ifndef GZ_PLUGIN_TEST_INTEGRATION_PLUGIN_UNLOAD_HH
19+
#define GZ_PLUGIN_TEST_INTEGRATION_PLUGIN_UNLOAD_HH
20+
21+
#include <gtest/gtest.h>
22+
23+
#include <iostream>
24+
#include <string>
25+
#include <unordered_set>
26+
27+
#include "../plugins/InstanceCounter.hh"
28+
#include "gz/plugin/Loader.hh"
29+
#include "gz/plugin/PluginPtr.hh"
30+
#include "gz/plugin/SpecializedPluginPtr.hh"
31+
32+
/////////////////////////////////////////////////
33+
/// \brief Load the InstanceCounter plugin
34+
/// \param[in] _nodelete True if RTLD_NODELETE should be used when loading the
35+
/// \return Pointer to the plugin
36+
gz::plugin::PluginPtr LoadInstanceCounter(bool _nodelete)
37+
{
38+
gz::plugin::Loader pl;
39+
40+
std::unordered_set<std::string> pluginNames =
41+
pl.LoadLib(GzInstanceCounter_LIB, _nodelete);
42+
43+
return pl.Instantiate("test::util::InstanceCounter");
44+
}
45+
46+
/////////////////////////////////////////////////
47+
/// \brief Load plugin, Instantiate the InstanceCounter, check the number of
48+
/// instances, and finally unload the plugin. Note, the plugin is unloaded when
49+
/// `instanceCounterPlugin` goes out of scope.
50+
/// \param[in] _nodelete True if RTLD_NODELETE should be used when loading the
51+
/// library.
52+
/// \param[in] _numExpectedInstances Expected number of instances of the plugin.
53+
void LoadAndTestInstanceCounter(bool _nodelete, int _numExpectedInstances)
54+
{
55+
gz::plugin::PluginPtr instanceCounterPlugin = LoadInstanceCounter(_nodelete);
56+
test::util::InstanceCounterBase *instanceCounter =
57+
instanceCounterPlugin->QueryInterface<test::util::InstanceCounterBase>();
58+
ASSERT_NE(nullptr, instanceCounter);
59+
60+
EXPECT_EQ(_numExpectedInstances, instanceCounter->Instances());
61+
}
62+
63+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (C) 2022 Open Source Robotics Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
#include <gz/utils/ExtraTestMacros.hh>
19+
20+
#include "plugin_unload.hh"
21+
22+
/////////////////////////////////////////////////
23+
TEST(PluginsWithNoDelete,
24+
GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(AreNotDeletedOnUnload))
25+
{
26+
LoadAndTestInstanceCounter(true, 1);
27+
// Since the plugin is not deleted on unload, the second time we load the
28+
// plugin, the instance count is incremenetd.
29+
LoadAndTestInstanceCounter(true, 2);
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (C) 2022 Open Source Robotics Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
#include <gz/utils/ExtraTestMacros.hh>
19+
20+
#include "plugin_unload.hh"
21+
22+
/////////////////////////////////////////////////
23+
TEST(PluginsWithoutNoDelete,
24+
GZ_UTILS_TEST_ENABLED_ONLY_ON_LINUX(AreDeletedOnUnload))
25+
{
26+
LoadAndTestInstanceCounter(false, 1);
27+
// Unlike the test in *with* nodelete, the number of instances will remain 1
28+
// after loading the plugin a second time.
29+
LoadAndTestInstanceCounter(false, 1);
30+
}

test/plugins/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ add_library(GzDummyPlugins SHARED
1010
DummyPlugins.cc
1111
DummyPluginsOtherTranslationUnit.cc)
1212

13+
add_library(GzInstanceCounter SHARED InstanceCounter.cc)
14+
1315
add_library(GzDummyStaticPlugin STATIC DummyStaticPlugin.cc)
1416

1517
# Create a variable for the name of the header which will contain the dummy plugin path.
@@ -23,6 +25,7 @@ foreach(plugin_target
2325
GzDummyPlugins
2426
GzFactoryPlugins
2527
GzTemplatedPlugins
28+
GzInstanceCounter
2629
GzDummyStaticPlugin)
2730

2831
target_link_libraries(${plugin_target} PRIVATE

test/plugins/InstanceCounter.cc

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (C) 2022 Open Source Robotics Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
#include <gz/plugin/Register.hh>
19+
20+
#include "InstanceCounter.hh"
21+
22+
namespace test
23+
{
24+
namespace util
25+
{
26+
27+
namespace {
28+
29+
/////////////////////////////////////////////////
30+
int numInstancesImpl() {
31+
// Use a static variable that never gets deleted as a way to demonstrate
32+
// that plugins with RTLD_NODELETE are not deleted when unloaded.
33+
static int *instances = new int(0);
34+
++(*instances);
35+
return *instances;
36+
}
37+
}
38+
39+
/// \brief A class that counts the number of times a plugin has been
40+
/// instantiated.
41+
class InstanceCounter : public InstanceCounterBase
42+
{
43+
public: InstanceCounter() : instances(numInstancesImpl())
44+
{
45+
}
46+
47+
public: ~InstanceCounter() override = default;
48+
49+
public: int Instances() override
50+
{
51+
return this->instances;
52+
}
53+
54+
private: int instances;
55+
};
56+
57+
GZ_ADD_PLUGIN(InstanceCounter, InstanceCounterBase)
58+
} // namespace util
59+
} // namespace test

test/plugins/InstanceCounter.hh

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (C) 2022 Open Source Robotics Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
#ifndef GZ_PLUGIN_TEST_PLUGINS_INSTANCECOUNTER_HH_
19+
#define GZ_PLUGIN_TEST_PLUGINS_INSTANCECOUNTER_HH_
20+
21+
namespace test
22+
{
23+
namespace util
24+
{
25+
/// \brief Get the instance count of this plugin, that's the number of times
26+
/// this plugin has been instantiated in this process.
27+
class InstanceCounterBase
28+
{
29+
public: virtual ~InstanceCounterBase() = default;
30+
31+
public: virtual int Instances() = 0;
32+
};
33+
34+
} // namespace util
35+
} // namespace test
36+
#endif

0 commit comments

Comments
 (0)