GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/execution_context.hpp
Date: 2026-01-18 18:26:31
Exec Total Coverage
Lines: 29 29 100.0%
Functions: 34 34 100.0%
Branches: 7 11 63.6%

Line Branch Exec Source
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/capy
8 //
9
10 #ifndef BOOST_CAPY_EXECUTION_CONTEXT_HPP
11 #define BOOST_CAPY_EXECUTION_CONTEXT_HPP
12
13 #include <boost/capy/detail/config.hpp>
14 #include <boost/capy/concept/executor.hpp>
15 #include <concepts>
16 #include <mutex>
17 #include <tuple>
18 #include <type_traits>
19 #include <typeindex>
20 #include <utility>
21
22 namespace boost {
23 namespace capy {
24
25 /** Base class for I/O object containers providing service management.
26
27 An execution context represents a place where function objects are
28 executed. It provides a service registry where polymorphic services
29 can be stored and retrieved by type. Each service type may be stored
30 at most once. Services may specify a nested `key_type` to enable
31 lookup by a base class type.
32
33 Derived classes such as `io_context` extend this to provide
34 execution facilities like event loops and thread pools. Derived
35 class destructors must call `shutdown()` and `destroy()` to ensure
36 proper service cleanup before member destruction.
37
38 @par Service Lifecycle
39 Services are created on first use via `use_service()` or explicitly
40 via `make_service()`. During destruction, `shutdown()` is called on
41 each service in reverse order of creation, then `destroy()` deletes
42 them. Both functions are idempotent.
43
44 @par Thread Safety
45 Service registration and lookup functions are thread-safe.
46 The `shutdown()` and `destroy()` functions are not thread-safe
47 and must only be called during destruction.
48
49 @par Example
50 @code
51 struct file_service : execution_context::service
52 {
53 protected:
54 void shutdown() override {}
55 };
56
57 struct posix_file_service : file_service
58 {
59 using key_type = file_service;
60
61 explicit posix_file_service(execution_context&) {}
62 };
63
64 class io_context : public execution_context
65 {
66 public:
67 ~io_context()
68 {
69 shutdown();
70 destroy();
71 }
72 };
73
74 io_context ctx;
75 ctx.make_service<posix_file_service>();
76 ctx.find_service<file_service>(); // returns posix_file_service*
77 ctx.find_service<posix_file_service>(); // also works
78 @endcode
79
80 @see service, is_execution_context
81 */
82 class BOOST_CAPY_DECL
83 execution_context
84 {
85 template<class T, class = void>
86 struct get_key : std::false_type
87 {};
88
89 template<class T>
90 struct get_key<T, std::void_t<typename T::key_type>> : std::true_type
91 {
92 using type = typename T::key_type;
93 };
94
95 public:
96 //------------------------------------------------
97
98 /** Abstract base class for services owned by an execution context.
99
100 Services provide extensible functionality to an execution context.
101 Each service type can be registered at most once. Services are
102 created via `use_service()` or `make_service()` and are owned by
103 the execution context for their lifetime.
104
105 Derived classes must implement the pure virtual `shutdown()` member
106 function, which is called when the owning execution context is
107 being destroyed. The `shutdown()` function should release resources
108 and cancel outstanding operations without blocking.
109
110 @par Deriving from service
111 @li Implement `shutdown()` to perform cleanup.
112 @li Accept `execution_context&` as the first constructor parameter.
113 @li Optionally define `key_type` to enable base-class lookup.
114
115 @par Example
116 @code
117 struct my_service : execution_context::service
118 {
119 explicit my_service(execution_context&) {}
120
121 protected:
122 void shutdown() override
123 {
124 // Cancel pending operations, release resources
125 }
126 };
127 @endcode
128
129 @see execution_context
130 */
131 class BOOST_CAPY_DECL
132 service
133 {
134 public:
135 66 virtual ~service() = default;
136
137 protected:
138 33 service() = default;
139
140 /** Called when the owning execution context shuts down.
141
142 Implementations should release resources and cancel any
143 outstanding asynchronous operations. This function must
144 not block and must not throw exceptions. Services are
145 shut down in reverse order of creation.
146
147 @par Exception Safety
148 No-throw guarantee.
149 */
150 virtual void shutdown() = 0;
151
152 private:
153 friend class execution_context;
154
155 service* next_ = nullptr;
156 #ifdef _MSC_VER
157 # pragma warning(push)
158 # pragma warning(disable: 4251)
159 #endif
160 std::type_index t0_ = typeid(void);
161 std::type_index t1_ = typeid(void);
162 #ifdef _MSC_VER
163 # pragma warning(pop)
164 #endif
165 };
166
167 //------------------------------------------------
168
169 execution_context(execution_context const&) = delete;
170
171 execution_context& operator=(execution_context const&) = delete;
172
173 /** Destructor.
174
175 Calls `shutdown()` then `destroy()` to clean up all services.
176
177 @par Effects
178 All services are shut down and deleted in reverse order
179 of creation.
180
181 @par Exception Safety
182 No-throw guarantee.
183 */
184 ~execution_context();
185
186 /** Default constructor.
187
188 @par Exception Safety
189 Strong guarantee.
190 */
191 execution_context();
192
193 /** Return true if a service of type T exists.
194
195 @par Thread Safety
196 Thread-safe.
197
198 @tparam T The type of service to check.
199
200 @return `true` if the service exists.
201 */
202 template<class T>
203 24 bool has_service() const noexcept
204 {
205 24 return find_service<T>() != nullptr;
206 }
207
208 /** Return a pointer to the service of type T, or nullptr.
209
210 @par Thread Safety
211 Thread-safe.
212
213 @tparam T The type of service to find.
214
215 @return A pointer to the service, or `nullptr` if not present.
216 */
217 template<class T>
218 40 T* find_service() const noexcept
219 {
220 40 std::lock_guard<std::mutex> lock(mutex_);
221 40 return static_cast<T*>(find_impl(typeid(T)));
222 40 }
223
224 /** Return a reference to the service of type T, creating it if needed.
225
226 If no service of type T exists, one is created by calling
227 `T(execution_context&)`. If T has a nested `key_type`, the
228 service is also indexed under that type.
229
230 @par Constraints
231 @li `T` must derive from `service`.
232 @li `T` must be constructible from `execution_context&`.
233
234 @par Exception Safety
235 Strong guarantee. If service creation throws, the container
236 is unchanged.
237
238 @par Thread Safety
239 Thread-safe.
240
241 @tparam T The type of service to retrieve or create.
242
243 @return A reference to the service.
244 */
245 template<class T>
246 57 T& use_service()
247 {
248 static_assert(std::is_base_of<service, T>::value,
249 "T must derive from service");
250 static_assert(std::is_constructible<T, execution_context&>::value,
251 "T must be constructible from execution_context&");
252
253 struct impl : factory
254 {
255 42 impl()
256 : factory(
257 typeid(T),
258 get_key<T>::value
259 ? typeid(typename get_key<T>::type)
260 42 : typeid(T))
261 {
262 42 }
263
264 26 service* create(execution_context& ctx) override
265 {
266
1/3
✓ Branch 2 taken 18 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
26 return new T(ctx);
267 }
268 };
269
270 57 impl f;
271
1/1
✓ Branch 1 taken 42 times.
114 return static_cast<T&>(use_service_impl(f));
272 }
273
274 /** Construct and add a service.
275
276 A new service of type T is constructed using the provided
277 arguments and added to the container. If T has a nested
278 `key_type`, the service is also indexed under that type.
279
280 @par Constraints
281 @li `T` must derive from `service`.
282 @li `T` must be constructible from `execution_context&, Args...`.
283 @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
284
285 @par Exception Safety
286 Strong guarantee. If service creation throws, the container
287 is unchanged.
288
289 @par Thread Safety
290 Thread-safe.
291
292 @throws std::invalid_argument if a service of the same type
293 or `key_type` already exists.
294
295 @tparam T The type of service to create.
296
297 @param args Arguments forwarded to the constructor of T.
298
299 @return A reference to the created service.
300 */
301 template<class T, class... Args>
302 18 T& make_service(Args&&... args)
303 {
304 static_assert(std::is_base_of<service, T>::value,
305 "T must derive from service");
306 if constexpr(get_key<T>::value)
307 {
308 static_assert(
309 std::is_convertible<T&, typename get_key<T>::type&>::value,
310 "T& must be convertible to key_type&");
311 }
312
313 struct impl : factory
314 {
315 std::tuple<Args&&...> args_;
316
317 10 explicit impl(Args&&... a)
318 : factory(
319 typeid(T),
320 get_key<T>::value
321 ? typeid(typename get_key<T>::type)
322 : typeid(T))
323 10 , args_(std::forward<Args>(a)...)
324 {
325 10 }
326
327 7 service* create(execution_context& ctx) override
328 {
329
1/1
✓ Branch 1 taken 1 times.
20 return std::apply([&ctx](auto&&... a) {
330
1/3
✓ Branch 4 taken 1 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
9 return new T(ctx, std::forward<decltype(a)>(a)...);
331 21 }, std::move(args_));
332 }
333 };
334
335
2/2
✓ Branch 4 taken 2 times.
✓ Branch 2 taken 6 times.
18 impl f(std::forward<Args>(args)...);
336
1/1
✓ Branch 1 taken 7 times.
31 return static_cast<T&>(make_service_impl(f));
337 }
338
339 protected:
340 /** Shut down all services.
341
342 Calls `shutdown()` on each service in reverse order of creation.
343 After this call, services remain allocated but are in a stopped
344 state. Derived classes should call this in their destructor
345 before any members are destroyed. This function is idempotent;
346 subsequent calls have no effect.
347
348 @par Effects
349 Each service's `shutdown()` member function is invoked once.
350
351 @par Postconditions
352 @li All services are in a stopped state.
353
354 @par Exception Safety
355 No-throw guarantee.
356
357 @par Thread Safety
358 Not thread-safe. Must not be called concurrently with other
359 operations on this execution_context.
360 */
361 void shutdown() noexcept;
362
363 /** Destroy all services.
364
365 Deletes all services in reverse order of creation. Derived
366 classes should call this as the final step of destruction.
367 This function is idempotent; subsequent calls have no effect.
368
369 @par Preconditions
370 @li `shutdown()` has been called.
371
372 @par Effects
373 All services are deleted and removed from the container.
374
375 @par Postconditions
376 @li The service container is empty.
377
378 @par Exception Safety
379 No-throw guarantee.
380
381 @par Thread Safety
382 Not thread-safe. Must not be called concurrently with other
383 operations on this execution_context.
384 */
385 void destroy() noexcept;
386
387 private:
388 struct BOOST_CAPY_DECL
389 factory
390 {
391 #ifdef _MSC_VER
392 # pragma warning(push)
393 # pragma warning(disable: 4251)
394 #endif
395 std::type_index t0;
396 std::type_index t1;
397 #ifdef _MSC_VER
398 # pragma warning(pop)
399 #endif
400
401 52 factory(std::type_index t0_, std::type_index t1_)
402 52 : t0(t0_), t1(t1_)
403 {
404 52 }
405
406 virtual service* create(execution_context&) = 0;
407
408 protected:
409 ~factory() = default;
410 };
411
412 service* find_impl(std::type_index ti) const noexcept;
413 service& use_service_impl(factory& f);
414 service& make_service_impl(factory& f);
415
416 #ifdef _MSC_VER
417 # pragma warning(push)
418 # pragma warning(disable: 4251)
419 #endif
420 mutable std::mutex mutex_;
421 #ifdef _MSC_VER
422 # pragma warning(pop)
423 #endif
424 service* head_ = nullptr;
425 bool shutdown_ = false;
426 };
427
428 } // namespace capy
429 } // namespace boost
430
431 #endif
432