|
| 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_ |
0 commit comments