GCC Code Coverage Report


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