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