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 ¶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, 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 ¶m) const { | |
63 | 40486701 | return ConcreteProductT::WillHandle(param); | |
64 | } | ||
65 | 6397132 | inline AbstractProductT *Construct(const ParameterT ¶m) 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 ¶m)` 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 ¶m)` 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 ¶m) { | |
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 |