Skip to content

Commit ccde71a

Browse files
committed
SIM: Example for loading simulation libraries dynamically
Signed-off-by: Felix Schlepper <felix.schlepper@cern.ch>
1 parent b35085d commit ccde71a

13 files changed

Lines changed: 296 additions & 25 deletions

File tree

Common/SimConfig/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ o2_add_library(SimConfig
2727
o2_target_root_dictionary(SimConfig
2828
HEADERS include/SimConfig/SimConfig.h
2929
include/SimConfig/SimParams.h
30+
include/SimConfig/SimDLLoader.h
3031
include/SimConfig/SimUserDecay.h
3132
include/SimConfig/InteractionDiamondParam.h
3233
include/SimConfig/DigiParams.h
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
#ifndef SIMDLLOADER_H_
13+
#define SIMDLLOADER_H_
14+
15+
#include "CommonUtils/DLLoader.h"
16+
17+
namespace o2::conf
18+
{
19+
class SimDLLoader : public o2::utils::DLLoaderBase<SimDLLoader>
20+
{
21+
friend class o2::utils::DLLoaderBase<DLLoaderBase>;
22+
};
23+
24+
} // namespace o2::conf
25+
26+
#endif // SIMDLLOADER_H_

Common/SimConfig/src/SimConfig.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ bool SimConfig::determineActiveModulesVersion(const std::string& version, const
222222

223223
// Filter skipped modules
224224
for (auto& s : skippedModules) {
225-
if(s.empty()){
225+
if (s.empty()) {
226226
continue;
227227
}
228228
auto iter = std::find(activeModules.begin(), activeModules.end(), s);

Common/Utils/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ o2_add_library(CommonUtils
2525
src/IRFrameSelector.cxx
2626
src/DebugStreamer.cxx
2727
PUBLIC_LINK_LIBRARIES ROOT::Hist ROOT::Tree Boost::iostreams O2::CommonDataFormat O2::Headers
28-
FairLogger::FairLogger O2::MathUtils TBB::tbb)
28+
FairLogger::FairLogger O2::MathUtils TBB::tbb Boost::boost)
2929

3030
o2_target_root_dictionary(CommonUtils
3131
HEADERS include/CommonUtils/TreeStream.h
@@ -45,6 +45,7 @@ o2_target_root_dictionary(CommonUtils
4545
include/CommonUtils/KeyValParam.h
4646
include/CommonUtils/VerbosityConfig.h
4747
include/CommonUtils/FileFetcher.h
48+
include/CommonUtils/DLLoader.h
4849
include/CommonUtils/NameConf.h
4950
include/CommonUtils/IRFrameSelector.h
5051
include/CommonUtils/DebugStreamer.h)
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
// \brief A thin wrapper to manage dynamic library loading, based around boost::dll
13+
14+
#ifndef DLLOADER_H_
15+
#define DLLOADER_H_
16+
17+
#include "Framework/Logger.h"
18+
19+
#include <boost/dll.hpp>
20+
#include <boost/function.hpp>
21+
#include <boost/core/demangle.hpp>
22+
23+
#include <filesystem>
24+
#include <optional>
25+
#include <unordered_map>
26+
#include <memory>
27+
#include <mutex>
28+
#include <cstdlib>
29+
#include <functional>
30+
#include <typeinfo>
31+
32+
namespace o2::utils
33+
{
34+
35+
// Manages dynamic loading and unloading (or rather ensuring they are not
36+
// unloaded for the duration of the program) of libraries and symbol lookups. It
37+
// ensures thread-safety through the use of a mutex and implements the Meyers
38+
// Singleton pattern to provide a single instance of the manager.
39+
template <typename DerivedType>
40+
class DLLoaderBase
41+
{
42+
43+
public:
44+
using library_t = std::shared_ptr<boost::dll::shared_library>;
45+
46+
// Returns the singleton instance of the manager. Any function should only be
47+
// accessed through an instance. This being a singleton serves two purposes:
48+
// 1. Libraries are loaded only once.
49+
// 2. Once loaded they are not again unloaded until the end of the program.
50+
static DerivedType& Instance()
51+
{
52+
static DerivedType instance;
53+
return instance;
54+
}
55+
56+
// Loads a dynamic library by its name and stores its handle. Returns true
57+
// if the library is successfully loaded or already loaded.
58+
bool addLibrary(const std::string& library)
59+
{
60+
const std::lock_guard<std::mutex> lock(mLock);
61+
62+
if (mLibraries.find(library) != mLibraries.end()) {
63+
return true; // Library already loaded
64+
}
65+
66+
if (mO2Path.empty()) {
67+
if (const auto* path = std::getenv("O2_ROOT")) {
68+
mO2Path = path;
69+
} else {
70+
LOGP(error, "$O2_ROOT not set!");
71+
return false;
72+
}
73+
}
74+
75+
auto path = getO2Path(library);
76+
if (auto dpath{boost::dll::shared_library::decorate(path)}; !std::filesystem::exists(dpath.c_str())) {
77+
LOGP(error, "Library under '{}' does not exist!", dpath.c_str());
78+
return false;
79+
}
80+
81+
try {
82+
auto lib = std::make_shared<boost::dll::shared_library>(path,
83+
boost::dll::load_mode::append_decorations |
84+
boost::dll::load_mode::rtld_lazy);
85+
if (lib == nullptr) {
86+
throw std::runtime_error("Library handle is nullptr!");
87+
}
88+
mLibraries[library] = std::move(lib);
89+
LOGP(info, "Loaded dynamic library '{}' from '{}'", library, path);
90+
return true;
91+
} catch (std::exception& e) {
92+
LOGP(error, "Failed to load library (path='{}'), failed reason: '{}'", boost::dll::shared_library::decorate(path).c_str(), e.what());
93+
return false;
94+
} catch (...) {
95+
LOGP(error, "Failed to load library (path='{}') for unknown reason!", boost::dll::shared_library::decorate(path).c_str());
96+
return false;
97+
}
98+
}
99+
100+
// Checks if a library contains a specific symbol.
101+
bool hasSymbol(const std::string& library, const std::string& symbol)
102+
{
103+
if (mLibraries.find(library) == mLibraries.end()) {
104+
// Library not loaded, attempt to load it
105+
if (!addLibrary(library)) {
106+
return false;
107+
}
108+
}
109+
110+
return mLibraries[library]->has(symbol);
111+
}
112+
113+
// Gets a function alias from a loaded library.
114+
template <typename ProtoType>
115+
std::optional<boost::function<ProtoType>> getFunctionAlias(const std::string& library, const std::string& fname)
116+
{
117+
if (fname.empty()) {
118+
LOGP(error, "Function name cannot be empty!");
119+
return std::nullopt;
120+
}
121+
122+
if (mLibraries.find(library) == mLibraries.end()) {
123+
// Library not loaded, attempt to load it
124+
if (!addLibrary(library)) {
125+
return std::nullopt;
126+
}
127+
}
128+
129+
const std::lock_guard<std::mutex> lock(mLock);
130+
const auto& lib = *mLibraries[library];
131+
if (!lib.has(fname)) {
132+
LOGP(error, "Library '{}' does not have a symbol '{}'", library, fname);
133+
return std::nullopt;
134+
}
135+
136+
boost::function<ProtoType> func = boost::dll::import_alias<ProtoType>(lib, fname);
137+
if (func.empty()) {
138+
LOGP(error, "Library '{}' does not have a symbol '{}' with {}", library, fname, getTypeName<ProtoType>());
139+
return std::nullopt;
140+
}
141+
return func;
142+
}
143+
144+
// Immediatley executes a function alias from a loaded library.
145+
template <typename Ret, typename... Args>
146+
Ret executeFunctionAlias(const std::string& library, const std::string& fname, Args... args)
147+
{
148+
using ProtoType = Ret(Args...);
149+
if (auto func = getFunctionAlias<ProtoType>(library, fname)) {
150+
return (*func)(args...);
151+
} else {
152+
throw std::runtime_error(fmt::format("Cannot get '{}' from '{}'", fname, library));
153+
}
154+
}
155+
156+
// Prints information about the loaded libraries and their symbols.
157+
void print(bool verbose = false)
158+
{
159+
const std::lock_guard<std::mutex> lock(mLock);
160+
161+
if (mO2Path.empty() || mLibraries.empty()) {
162+
LOGP(info, "No libraries added!");
163+
}
164+
165+
LOGP(info, "Printing loaded dynamic library information:");
166+
for (int i{0}; const auto& [library, handle] : mLibraries) {
167+
LOGP(info, " {: <3d}: {}", i++, library);
168+
if (verbose) {
169+
boost::dll::library_info libInfo{boost::dll::shared_library::decorate(getO2Path(library))};
170+
for (int j{0}; const auto& sec : libInfo.sections()) {
171+
LOGP(info, " SECTION {: <3d}: {}", j++, sec);
172+
for (int k{0}; const auto& symb : libInfo.symbols(sec)) {
173+
LOGP(info, " SYMBOL {: <3d}: {}", k++, symb);
174+
}
175+
}
176+
}
177+
}
178+
}
179+
180+
// Delete copy and move constructors to enforce singleton pattern.
181+
DLLoaderBase(const DLLoaderBase&) = delete;
182+
DLLoaderBase& operator=(const DLLoaderBase&) = delete;
183+
DLLoaderBase(DLLoaderBase&&) = delete;
184+
DLLoaderBase& operator=(DLLoaderBase&&) = delete;
185+
186+
protected:
187+
// Constructor and destructor are protected to enforce singleton pattern.
188+
DLLoaderBase()
189+
{
190+
LOGP(info, "{} instance created", getTypeName<DerivedType>());
191+
}
192+
~DLLoaderBase() = default;
193+
194+
private:
195+
// Returns the full path to a O2 shared library..
196+
[[nodiscard]] std::string getO2Path(const std::string& library) const
197+
{
198+
return mO2Path + "/lib/" + library;
199+
}
200+
201+
// Returns the demangled type name of a prototype, e.g., for pretty printing.
202+
template <typename ProtoType>
203+
[[nodiscard]] auto getTypeName() -> std::string
204+
{
205+
return boost::core::demangle(typeid(ProtoType).name());
206+
}
207+
208+
std::unordered_map<std::string, library_t> mLibraries{};
209+
std::mutex mLock{};
210+
std::string mO2Path{};
211+
};
212+
213+
} // namespace o2::utils
214+
215+
#endif // DLLOADER_H_

Detectors/ITSMFT/ITS/simulation/include/ITSSimulation/Detector.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
#include "TLorentzVector.h" // for TLorentzVector
2828
#include "TVector3.h" // for TVector3
2929

30+
#include <boost/dll/alias.hpp> // for BOOST_DLL_ALIAS
31+
3032
#ifdef ENABLE_UPGRADES
3133
#include "ITS3Simulation/DescriptorInnerBarrelITS3.h" // for Description of Inner Barrel (ITS3)
3234
#endif
@@ -79,6 +81,12 @@ class Detector : public o2::base::DetImpl<Detector>
7981
/// kFALSE for inactive detectors
8082
Detector(Bool_t active, TString name = "ITS");
8183

84+
// Factory method
85+
static o2::base::Detector* create(const char* name, bool active)
86+
{
87+
return new Detector(active, name);
88+
}
89+
8290
/// Default constructor
8391
Detector();
8492

Detectors/ITSMFT/ITS/simulation/src/Detector.cxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,3 +1317,5 @@ Hit* Detector::addHit(int trackID, int detID, const TVector3& startPos, const TV
13171317
}
13181318

13191319
ClassImp(o2::its::Detector);
1320+
1321+
BOOST_DLL_ALIAS(o2::its::Detector::create, create_Detector_its)

Detectors/TPC/simulation/include/TPCSimulation/Detector.h

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,17 @@ class Detector : public o2::base::DetImpl<Detector>
6363
kAlumina1 = 26
6464
};
6565
/** Name : Detector Name
66-
* Active: kTRUE for active detectors (ProcessHits() will be called)
67-
* kFALSE for inactive detectors
68-
*/
66+
* Active: kTRUE for active detectors (ProcessHits() will be called)
67+
* kFALSE for inactive detectors
68+
*/
6969
Detector(Bool_t Active);
7070

71+
// Factory method
72+
static o2::base::Detector* create(bool active)
73+
{
74+
return new Detector(active);
75+
}
76+
7177
/** default constructor */
7278
Detector();
7379

@@ -78,8 +84,8 @@ class Detector : public o2::base::DetImpl<Detector>
7884
void InitializeO2Detector() override;
7985

8086
/** this method is called for each step during simulation
81-
* (see FairMCApplication::Stepping())
82-
*/
87+
* (see FairMCApplication::Stepping())
88+
*/
8389
// virtual Bool_t ProcessHitsOrig( FairVolume* v=0);
8490
Bool_t ProcessHits(FairVolume* v = nullptr) override;
8591

@@ -105,8 +111,8 @@ class Detector : public o2::base::DetImpl<Detector>
105111
void ConstructGeometry() override;
106112

107113
/** This method is an example of how to add your own point
108-
* of type DetectorPoint to the clones array
109-
*/
114+
* of type DetectorPoint to the clones array
115+
*/
110116
Point* addHit(float x, float y, float z, float time, float nElectrons, float trackID, float detID);
111117

112118
/// Copied from AliRoot - should go to someplace else
@@ -130,8 +136,8 @@ class Detector : public o2::base::DetImpl<Detector>
130136
Double_t Gamma(Double_t k);
131137

132138
/** The following methods can be implemented if you need to make
133-
* any optional action in your detector during the transport.
134-
*/
139+
* any optional action in your detector during the transport.
140+
*/
135141

136142
void EndOfEvent() override;
137143
void FinishPrimary() override { ; }

Detectors/TPC/simulation/src/Detector.cxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,3 +3216,6 @@ std::string Detector::getHitBranchNames(int probe) const
32163216
}
32173217

32183218
ClassImp(o2::tpc::Detector);
3219+
3220+
#include <boost/dll/alias.hpp>
3221+
BOOST_DLL_ALIAS(o2::tpc::Detector::create, create_Detector_tpc)

Detectors/Upgrades/ALICE3/TRK/simulation/include/TRKSimulation/Detector.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ class Detector : public o2::base::DetImpl<Detector>
3434
Detector();
3535
~Detector();
3636

37+
// Factory method
38+
static o2::base::Detector* create(bool active)
39+
{
40+
return new Detector(active);
41+
}
42+
3743
void ConstructGeometry() override;
3844

3945
o2::itsmft::Hit* addHit(int trackID, int detID, const TVector3& startPos, const TVector3& endPos,
@@ -104,4 +110,4 @@ struct UseShm<o2::trk::Detector> {
104110
} // namespace base
105111
} // namespace o2
106112
#endif
107-
#endif
113+
#endif

0 commit comments

Comments
 (0)