Directory: | cvmfs/ |
---|---|
File: | cvmfs/util/plugin.h |
Date: | 2025-06-29 02:35:41 |
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 | 10161 | 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 | 64391033 | inline bool WillHandle(const ParameterT ¶m) const { | |
64 | 64391033 | return ConcreteProductT::WillHandle(param); | |
65 | } | ||
66 | 16637724 | inline AbstractProductT *Construct(const ParameterT ¶m) const { | |
67 |
1/2✓ Branch 2 taken 9770228 times.
✗ Branch 3 not taken.
|
33273293 | AbstractProductT *product = new ConcreteProductT(param); |
68 | 33330545 | 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 | 552 | 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 | 33319860 | virtual ~PolymorphicConstructionImpl() { } | |
170 | |||
171 | 18938123 | static AbstractProductT *Construct(const ParameterT ¶m) { | |
172 |
1/2✓ Branch 1 taken 18946547 times.
✗ Branch 2 not taken.
|
18938123 | LazilyRegisterPlugins(); |
173 | |||
174 | // select and initialize the correct plugin at runtime | ||
175 | // (polymorphic construction) | ||
176 | 18946547 | typename RegisteredPlugins::const_iterator i = registered_plugins_.begin(); | |
177 | 18945962 | typename RegisteredPlugins::const_iterator iend = registered_plugins_.end(); | |
178 |
2/2✓ Branch 2 taken 32196516 times.
✓ Branch 3 taken 4040870 times.
|
36243821 | for (; i != iend; ++i) { |
179 |
3/4✓ Branch 2 taken 32194254 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16636944 times.
✓ Branch 5 taken 15557310 times.
|
32196516 | if ((*i)->WillHandle(param)) { |
180 | // create and initialize the class that claimed responsibility | ||
181 |
1/2✓ Branch 2 taken 16665999 times.
✗ Branch 3 not taken.
|
16636944 | AbstractProductT *product = (*i)->Construct(param); |
182 |
3/4✓ Branch 1 taken 16660695 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1736454 times.
✓ Branch 4 taken 14924241 times.
|
16665999 | if (!product->Initialize()) { |
183 |
1/2✓ Branch 0 taken 1736454 times.
✗ Branch 1 not taken.
|
1736454 | delete product; |
184 | 1736454 | continue; | |
185 | } | ||
186 | 14924241 | return product; | |
187 | } | ||
188 | } | ||
189 | |||
190 | // no plugin found to handle the given parameter... | ||
191 | 4040870 | return NULL; | |
192 | } | ||
193 | |||
194 | protected: | ||
195 | 18938956 | 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 2684 times.
✓ Branch 2 taken 18965561 times.
|
18938956 | if (atomic_read32(&needs_init_)) { |
205 | 2684 | MutexLockGuard m(&init_mutex_); | |
206 |
1/2✓ Branch 1 taken 2684 times.
✗ Branch 2 not taken.
|
2684 | if (atomic_read32(&needs_init_)) { |
207 |
1/2✓ Branch 1 taken 2684 times.
✗ Branch 2 not taken.
|
2684 | AbstractProductT::RegisterPlugins(); |
208 | 2684 | atomic_dec32(&needs_init_); | |
209 | } | ||
210 | 2684 | } | |
211 | |||
212 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 18947380 times.
|
18968245 | assert(!registered_plugins_.empty()); |
213 | 18947380 | } | |
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 | 18167 | static void RegisterPlugin() { | |
233 | 18167 | registered_plugins_.push_back( | |
234 |
2/4✓ Branch 2 taken 10161 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 10161 times.
✗ Branch 6 not taken.
|
18167 | new AbstractFactoryImpl<ConcreteProductT, AbstractProductT, ParameterT, |
235 | InfoT>()); | ||
236 | 18167 | } | |
237 | |||
238 | 9763937 | 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 | 2661 | static void UnregisterAllPlugins() { | |
253 | 2661 | registered_plugins_.clear(); | |
254 | 2661 | needs_init_ = 1; | |
255 | 2661 | } | |
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 | 92 | static IntrospectionData Introspect() { | |
284 | 92 | IntrospectionData introspection_data; | |
285 |
1/2✓ Branch 2 taken 92 times.
✗ Branch 3 not taken.
|
92 | introspection_data.reserve(T::registered_plugins_.size()); |
286 | 92 | const RegisteredPlugins &plugins = T::registered_plugins_; | |
287 | |||
288 |
1/2✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
|
92 | T::LazilyRegisterPlugins(); |
289 | 92 | typename RegisteredPlugins::const_iterator i = plugins.begin(); | |
290 | 92 | typename RegisteredPlugins::const_iterator iend = plugins.end(); | |
291 |
2/2✓ Branch 1 taken 276 times.
✓ Branch 2 taken 92 times.
|
368 | for (; i != iend; ++i) { |
292 |
2/4✓ Branch 2 taken 276 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 276 times.
✗ Branch 6 not taken.
|
276 | introspection_data.push_back((*i)->Introspect()); |
293 | } | ||
294 | |||
295 | 184 | 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 |