| Directory: | cvmfs/ |
|---|---|
| File: | cvmfs/util/plugin.h |
| Date: | 2025-11-09 02:35:23 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 48 | 50 | 96.0% |
| Branches: | 25 | 40 | 62.5% |
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /** | ||
| 2 | * This file is part of the CernVM File System. | ||
| 3 | */ | ||
| 4 | |||
| 5 | #ifndef CVMFS_UTIL_PLUGIN_H_ | ||
| 6 | #define CVMFS_UTIL_PLUGIN_H_ | ||
| 7 | |||
| 8 | #include <pthread.h> | ||
| 9 | |||
| 10 | #include <cassert> | ||
| 11 | #include <cstddef> | ||
| 12 | #include <vector> | ||
| 13 | |||
| 14 | #include "util/atomic.h" | ||
| 15 | #include "util/mutex.h" | ||
| 16 | |||
| 17 | #ifdef CVMFS_NAMESPACE_GUARD | ||
| 18 | namespace CVMFS_NAMESPACE_GUARD { | ||
| 19 | #endif | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Used internally by the PolymorphicConstruction template | ||
| 23 | * Provides an abstract interface for Factory objects that allow the poly- | ||
| 24 | * morphic creation of arbitrary objects at runtime. | ||
| 25 | * | ||
| 26 | * @param AbstractProductT the abstract base class of all classes that could be | ||
| 27 | * polymorphically constructed by this factory | ||
| 28 | * @param ParameterT the type of the parameter that is used to figure out | ||
| 29 | * which class should be instantiated at runtime | ||
| 30 | * @param InfoT wrapper type for introspection data of registered | ||
| 31 | * plugins | ||
| 32 | */ | ||
| 33 | template<class AbstractProductT, typename ParameterT, typename InfoT> | ||
| 34 | class AbstractFactory { | ||
| 35 | public: | ||
| 36 | 6860 | AbstractFactory() { } | |
| 37 | ✗ | virtual ~AbstractFactory() { } | |
| 38 | |||
| 39 | virtual bool WillHandle(const ParameterT ¶m) const = 0; | ||
| 40 | virtual AbstractProductT *Construct(const ParameterT ¶m) const = 0; | ||
| 41 | virtual InfoT Introspect() const = 0; | ||
| 42 | }; | ||
| 43 | |||
| 44 | |||
| 45 | /** | ||
| 46 | * Implementation of the AbstractFactory template to wrap the creation of a | ||
| 47 | * specific class instance. Namely ConcreteProductT. (Note: still abstract) | ||
| 48 | * See the description of PolymorphicCreation for more details | ||
| 49 | * | ||
| 50 | * @param ConcreteProductT the class that will be instantiated by this factory | ||
| 51 | * class (must be derived from AbstractProductT) | ||
| 52 | * @param AbstractProductT the base class of all used ConcreteProductT classes | ||
| 53 | * @param ParameterT the type of the parameter that is used to poly- | ||
| 54 | * morphically create a specific ConcreteProductT | ||
| 55 | * @param InfoT wrapper type for introspection data of registered | ||
| 56 | * plugins | ||
| 57 | */ | ||
| 58 | template<class ConcreteProductT, class AbstractProductT, typename ParameterT, | ||
| 59 | typename InfoT> | ||
| 60 | class AbstractFactoryImpl2 | ||
| 61 | : public AbstractFactory<AbstractProductT, ParameterT, InfoT> { | ||
| 62 | public: | ||
| 63 | 31071303 | inline bool WillHandle(const ParameterT ¶m) const { | |
| 64 | 31071303 | return ConcreteProductT::WillHandle(param); | |
| 65 | } | ||
| 66 | 9786666 | inline AbstractProductT *Construct(const ParameterT ¶m) const { | |
| 67 |
1/2✓ Branch 2 taken 7265625 times.
✗ Branch 3 not taken.
|
19571787 | AbstractProductT *product = new ConcreteProductT(param); |
| 68 | 19619695 | return product; | |
| 69 | } | ||
| 70 | }; | ||
| 71 | |||
| 72 | |||
| 73 | /** | ||
| 74 | * Template to add an implementation of Introspect() based on the type of InfoT. | ||
| 75 | * Generally Introspect() will call ConcreteProductT::GetInfo() and return it's | ||
| 76 | * result. However if InfoT = void, this method still needs to be stubbed. | ||
| 77 | * (See also the template specialization for InfoT = void below) | ||
| 78 | */ | ||
| 79 | template<class ConcreteProductT, class AbstractProductT, typename ParameterT, | ||
| 80 | typename InfoT> | ||
| 81 | class AbstractFactoryImpl | ||
| 82 | : public AbstractFactoryImpl2<ConcreteProductT, AbstractProductT, | ||
| 83 | ParameterT, InfoT> { | ||
| 84 | 204 | inline InfoT Introspect() const { return ConcreteProductT::GetInfo(); } | |
| 85 | }; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Template specialization for InfoT = void that only stubs the abstract method | ||
| 89 | * Introspect(). | ||
| 90 | */ | ||
| 91 | template<class ConcreteProductT, class AbstractProductT, typename ParameterT> | ||
| 92 | class AbstractFactoryImpl<ConcreteProductT, AbstractProductT, ParameterT, void> | ||
| 93 | : public AbstractFactoryImpl2<ConcreteProductT, AbstractProductT, | ||
| 94 | ParameterT, void> { | ||
| 95 | ✗ | inline void Introspect() const { } | |
| 96 | }; | ||
| 97 | |||
| 98 | /** | ||
| 99 | * Template to simplify the polymorphic creation of a number of concrete classes | ||
| 100 | * that share the common base class AbstractProductT. Use this to create | ||
| 101 | * flexible class hierarchies. | ||
| 102 | * | ||
| 103 | * The template assumes a number of things from the user classes: | ||
| 104 | * 1. AbstractProductT must implement `static void RegisterPlugins()` which | ||
| 105 | * will register all available derived classes by calling | ||
| 106 | * `RegisterPlugin<DerivedClass>()` for each implemented sub-class. | ||
| 107 | * 2. Each derived class of AbstractProductT must implement | ||
| 108 | * `static bool WillHandle(const ParameterT ¶m)` that figures out if the | ||
| 109 | * concrete class can cope with the given parameter | ||
| 110 | * 3. Each derived class must have at least the following constructor: | ||
| 111 | * `DerivedClass(const ParameterT ¶m)` which is used to instantiate the | ||
| 112 | * concrete class in case it returned true in WillHandle() | ||
| 113 | * 4. (OPTIONAL) Both AbstractProductT and ConcreteProductTs can override the | ||
| 114 | * virtual method `bool Initialize()` which will be called directly after | ||
| 115 | * creation of a ConcreteProductT. If it returns false, the constructed in- | ||
| 116 | * stance is deleted and the list of plugins is traversed further. | ||
| 117 | * 5. (OPTIONAL) The ConcreteProductTs can implement a `static InfoT GetInfo()` | ||
| 118 | * that can be used for run-time introspection of registered plugins using | ||
| 119 | * PolymorphicConstruction<AbstractProductT, ParameterT, | ||
| 120 | * InfoT>::Introspect() | ||
| 121 | * | ||
| 122 | * A possible class hierarchy could look like this: | ||
| 123 | * | ||
| 124 | * PolymorphicConstruction<AbstractNumberCruncher, Parameter> | ||
| 125 | * | | ||
| 126 | * +--> AbstractNumberCruncher | ||
| 127 | * | | ||
| 128 | * +--> ConcreteMulticoreNumberCruncher | ||
| 129 | * | | ||
| 130 | * +--> ConcreteGpuNumberCruncher | ||
| 131 | * | | ||
| 132 | * +--> ConcreteClusterNumberCruncher | ||
| 133 | * | ||
| 134 | * In this example AbstractNumberCruncher::RegisterPlugins() will register all | ||
| 135 | * three concrete number cruncher classes. Using the whole thing would look like | ||
| 136 | * so: | ||
| 137 | * | ||
| 138 | * Parameter param = Parameter(typicalGpuProblem); | ||
| 139 | * AbstractNumberCruncher *polymorphicCruncher = | ||
| 140 | * AbstractNumberCruncher::Construct(param); | ||
| 141 | * polymorphicCruncher->Crunch(); | ||
| 142 | * | ||
| 143 | * `polymorphicCruncher` now points to an instance of ConcreteGpuNumberCruncher | ||
| 144 | * and can be used as any other polymorphic class with the interface defined in | ||
| 145 | * AbstractNumberCruncher. | ||
| 146 | * | ||
| 147 | * Note: PolymorphicCreation goes through the list of registered plugins in the | ||
| 148 | * order they have been registered and instantiates the first class that | ||
| 149 | * claims responsibility for the given parameter. | ||
| 150 | * | ||
| 151 | * @param AbstractProductT the common base class of all classes that should be | ||
| 152 | * polymorphically created. In most cases this will be | ||
| 153 | * the class that directly inherits from Polymorphic- | ||
| 154 | * Construction. | ||
| 155 | * @param ParameterT the type of the parameter that is used to poly- | ||
| 156 | * morphically instantiate one of the subclasses of | ||
| 157 | * AbstractProductT | ||
| 158 | * @param InfoT (optional) wrapper type for introspection data of | ||
| 159 | * registered plugins. InfoT | ||
| 160 | * AbstractProductT::GetInfo() needs to be implemented for each plugin | ||
| 161 | */ | ||
| 162 | template<class AbstractProductT, typename ParameterT, typename InfoT> | ||
| 163 | class PolymorphicConstructionImpl { | ||
| 164 | protected: | ||
| 165 | typedef AbstractFactory<AbstractProductT, ParameterT, InfoT> Factory; | ||
| 166 | typedef std::vector<Factory *> RegisteredPlugins; | ||
| 167 | |||
| 168 | public: | ||
| 169 | 19609672 | virtual ~PolymorphicConstructionImpl() { } | |
| 170 | |||
| 171 | 10632100 | static AbstractProductT *Construct(const ParameterT ¶m) { | |
| 172 |
1/2✓ Branch 1 taken 10642975 times.
✗ Branch 2 not taken.
|
10632100 | LazilyRegisterPlugins(); |
| 173 | |||
| 174 | // select and initialize the correct plugin at runtime | ||
| 175 | // (polymorphic construction) | ||
| 176 | 10642975 | typename RegisteredPlugins::const_iterator i = registered_plugins_.begin(); | |
| 177 | 10644541 | typename RegisteredPlugins::const_iterator iend = registered_plugins_.end(); | |
| 178 |
2/2✓ Branch 2 taken 15536221 times.
✓ Branch 3 taken 1490526 times.
|
17029299 | for (; i != iend; ++i) { |
| 179 |
3/4✓ Branch 2 taken 15532422 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9785245 times.
✓ Branch 5 taken 5747177 times.
|
15536221 | if ((*i)->WillHandle(param)) { |
| 180 | // create and initialize the class that claimed responsibility | ||
| 181 |
1/2✓ Branch 2 taken 9803515 times.
✗ Branch 3 not taken.
|
9785245 | AbstractProductT *product = (*i)->Construct(param); |
| 182 |
3/4✓ Branch 1 taken 9802529 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 635290 times.
✓ Branch 4 taken 9167239 times.
|
9803515 | if (!product->Initialize()) { |
| 183 |
1/2✓ Branch 0 taken 635290 times.
✗ Branch 1 not taken.
|
635290 | delete product; |
| 184 | 635290 | continue; | |
| 185 | } | ||
| 186 | 9167239 | return product; | |
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | // no plugin found to handle the given parameter... | ||
| 191 | 1490526 | return NULL; | |
| 192 | } | ||
| 193 | |||
| 194 | protected: | ||
| 195 | 10633729 | static void LazilyRegisterPlugins() { | |
| 196 | // Thread Safety Note: | ||
| 197 | // Double Checked Locking with atomics! | ||
| 198 | // Simply double checking registered_plugins_.empty() is _not_ thread safe | ||
| 199 | // since a second thread might find a registered_plugins_ list that is | ||
| 200 | // currently under construction and therefore _not_ empty but also _not_ | ||
| 201 | // fully initialized! | ||
| 202 | // See StackOverflow: | ||
| 203 | // http://stackoverflow.com/questions/8097439/lazy-initialized-caching-how-do-i-make-it-thread-safe | ||
| 204 |
2/2✓ Branch 1 taken 1788 times.
✓ Branch 2 taken 10662565 times.
|
10633729 | if (atomic_read32(&needs_init_)) { |
| 205 | 1788 | MutexLockGuard m(&init_mutex_); | |
| 206 |
1/2✓ Branch 1 taken 1788 times.
✗ Branch 2 not taken.
|
1788 | if (atomic_read32(&needs_init_)) { |
| 207 |
1/2✓ Branch 1 taken 1788 times.
✗ Branch 2 not taken.
|
1788 | AbstractProductT::RegisterPlugins(); |
| 208 | 1788 | atomic_dec32(&needs_init_); | |
| 209 | } | ||
| 210 | 1788 | } | |
| 211 | |||
| 212 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 10643531 times.
|
10664353 | assert(!registered_plugins_.empty()); |
| 213 | 10643531 | } | |
| 214 | |||
| 215 | /** | ||
| 216 | * Friend class for testability (see test/common/testutil.h) | ||
| 217 | */ | ||
| 218 | friend class PolymorphicConstructionUnittestAdapter; | ||
| 219 | |||
| 220 | /** | ||
| 221 | * Registers a plugin that is polymorphically constructable afterwards. | ||
| 222 | * Warning: Multiple registrations of the same ConcreteProductT might lead to | ||
| 223 | * undefined behaviour! | ||
| 224 | * | ||
| 225 | * @param ConcreteProductT the concrete implementation of AbstractProductT | ||
| 226 | * that should be registered as constructable. | ||
| 227 | * | ||
| 228 | * Note: You shall not need to use this method anywhere in your code | ||
| 229 | * except in AbstractProductT::RegisterPlugins(). | ||
| 230 | */ | ||
| 231 | template<class ConcreteProductT> | ||
| 232 | 12175 | static void RegisterPlugin() { | |
| 233 | 12175 | registered_plugins_.push_back( | |
| 234 |
2/4✓ Branch 2 taken 6860 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 6860 times.
✗ Branch 6 not taken.
|
12175 | new AbstractFactoryImpl<ConcreteProductT, AbstractProductT, ParameterT, |
| 235 | InfoT>()); | ||
| 236 | 12175 | } | |
| 237 | |||
| 238 | 7254446 | virtual bool Initialize() { return true; } | |
| 239 | |||
| 240 | private: | ||
| 241 | /** | ||
| 242 | * This method clears the list of registered plugins. | ||
| 243 | * Note: A user of PolymorphicConstruction is _not_ supposed to use this! The | ||
| 244 | * method is meant to be used solely for testing purposes! In particular | ||
| 245 | * a unit test registering a mocked plugin is supposed to clear up after | ||
| 246 | * _each_ unit test! see: gtest: SetUp() / TearDown() and | ||
| 247 | * PolymorphicConstructionUnittestAdapter | ||
| 248 | * | ||
| 249 | * DO NOT USE THIS OUTSIDE UNIT TESTS!! | ||
| 250 | * -> Global state is nasty! | ||
| 251 | */ | ||
| 252 | 1732 | static void UnregisterAllPlugins() { | |
| 253 | 1732 | registered_plugins_.clear(); | |
| 254 | 1732 | needs_init_ = 1; | |
| 255 | 1732 | } | |
| 256 | |||
| 257 | protected: | ||
| 258 | static RegisteredPlugins registered_plugins_; | ||
| 259 | |||
| 260 | private: | ||
| 261 | static atomic_int32 needs_init_; | ||
| 262 | static pthread_mutex_t init_mutex_; | ||
| 263 | }; | ||
| 264 | |||
| 265 | |||
| 266 | /** | ||
| 267 | * Interface template for PolymorphicConstruction. | ||
| 268 | * This adds the static method Introspect() to each PolymorphicConstruction type | ||
| 269 | * implementation if (and only if) InfoT is not void. | ||
| 270 | * Backward compatibility: if InfoT is not defined (i.e. is void), Introspect() | ||
| 271 | * is not defined at all! (see template specialization) | ||
| 272 | */ | ||
| 273 | template<class AbstractProductT, typename ParameterT, typename InfoT = void> | ||
| 274 | class PolymorphicConstruction | ||
| 275 | : public PolymorphicConstructionImpl<AbstractProductT, ParameterT, InfoT> { | ||
| 276 | private: | ||
| 277 | typedef PolymorphicConstructionImpl<AbstractProductT, ParameterT, InfoT> T; | ||
| 278 | typedef typename T::RegisteredPlugins RegisteredPlugins; | ||
| 279 | |||
| 280 | public: | ||
| 281 | typedef std::vector<InfoT> IntrospectionData; | ||
| 282 | |||
| 283 | 34 | static IntrospectionData Introspect() { | |
| 284 | 34 | IntrospectionData introspection_data; | |
| 285 |
1/2✓ Branch 2 taken 34 times.
✗ Branch 3 not taken.
|
34 | introspection_data.reserve(T::registered_plugins_.size()); |
| 286 | 34 | const RegisteredPlugins &plugins = T::registered_plugins_; | |
| 287 | |||
| 288 |
1/2✓ Branch 1 taken 34 times.
✗ Branch 2 not taken.
|
34 | T::LazilyRegisterPlugins(); |
| 289 | 34 | typename RegisteredPlugins::const_iterator i = plugins.begin(); | |
| 290 | 34 | typename RegisteredPlugins::const_iterator iend = plugins.end(); | |
| 291 |
2/2✓ Branch 1 taken 102 times.
✓ Branch 2 taken 34 times.
|
136 | for (; i != iend; ++i) { |
| 292 |
2/4✓ Branch 2 taken 102 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 102 times.
✗ Branch 6 not taken.
|
102 | introspection_data.push_back((*i)->Introspect()); |
| 293 | } | ||
| 294 | |||
| 295 | 68 | return introspection_data; | |
| 296 | } | ||
| 297 | }; | ||
| 298 | |||
| 299 | /** | ||
| 300 | * Template specialization for backward compatibility that _does not_ implement | ||
| 301 | * a static Introspect() method when the InfoT parameter is not given or is void | ||
| 302 | */ | ||
| 303 | template<class AbstractProductT, typename ParameterT> | ||
| 304 | class PolymorphicConstruction<AbstractProductT, ParameterT, void> | ||
| 305 | : public PolymorphicConstructionImpl<AbstractProductT, ParameterT, void> { | ||
| 306 | }; | ||
| 307 | |||
| 308 | |||
| 309 | template<class AbstractProductT, typename ParameterT, typename InfoT> | ||
| 310 | atomic_int32 PolymorphicConstructionImpl<AbstractProductT, ParameterT, | ||
| 311 | InfoT>::needs_init_ = 1; | ||
| 312 | |||
| 313 | template<class AbstractProductT, typename ParameterT, typename InfoT> | ||
| 314 | pthread_mutex_t | ||
| 315 | PolymorphicConstructionImpl<AbstractProductT, ParameterT, | ||
| 316 | InfoT>::init_mutex_ = PTHREAD_MUTEX_INITIALIZER; | ||
| 317 | |||
| 318 | // init the static member registered_plugins_ inside the | ||
| 319 | // PolymorphicConstructionImpl template... whoa, what ugly code :o) | ||
| 320 | template<class AbstractProductT, typename ParameterT, typename InfoT> | ||
| 321 | typename PolymorphicConstructionImpl<AbstractProductT, ParameterT, | ||
| 322 | InfoT>::RegisteredPlugins | ||
| 323 | PolymorphicConstructionImpl<AbstractProductT, ParameterT, | ||
| 324 | InfoT>::registered_plugins_; | ||
| 325 | |||
| 326 | |||
| 327 | #ifdef CVMFS_NAMESPACE_GUARD | ||
| 328 | } // namespace CVMFS_NAMESPACE_GUARD | ||
| 329 | #endif | ||
| 330 | |||
| 331 | #endif // CVMFS_UTIL_PLUGIN_H_ | ||
| 332 |