GCC Code Coverage Report


Directory: ./
File: libs/capy/include/boost/capy/ex/execution_context.hpp
Date: 2026-01-15 21:26:50
Exec Total Coverage
Lines: 29 29 100.0%
Functions: 31 31 100.0%
Branches: 6 8 75.0%

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 service
132 {
133 public:
134 30 virtual ~service() = default;
135
136 protected:
137 15 service() = default;
138
139 /** Called when the owning execution context shuts down.
140
141 Implementations should release resources and cancel any
142 outstanding asynchronous operations. This function must
143 not block and must not throw exceptions. Services are
144 shut down in reverse order of creation.
145
146 @par Exception Safety
147 No-throw guarantee.
148 */
149 virtual void shutdown() = 0;
150
151 private:
152 friend class execution_context;
153
154 service* next_ = nullptr;
155 std::type_index t0_ = typeid(void);
156 std::type_index t1_ = typeid(void);
157 };
158
159 //------------------------------------------------
160
161 execution_context(execution_context const&) = delete;
162
163 execution_context& operator=(execution_context const&) = delete;
164
165 /** Destructor.
166
167 Calls `shutdown()` then `destroy()` to clean up all services.
168
169 @par Effects
170 All services are shut down and deleted in reverse order
171 of creation.
172
173 @par Exception Safety
174 No-throw guarantee.
175 */
176 ~execution_context();
177
178 /** Default constructor.
179
180 @par Exception Safety
181 Strong guarantee.
182 */
183 execution_context();
184
185 /** Return true if a service of type T exists.
186
187 @par Thread Safety
188 Thread-safe.
189
190 @tparam T The type of service to check.
191
192 @return `true` if the service exists.
193 */
194 template<class T>
195 24 bool has_service() const noexcept
196 {
197 24 return find_service<T>() != nullptr;
198 }
199
200 /** Return a pointer to the service of type T, or nullptr.
201
202 @par Thread Safety
203 Thread-safe.
204
205 @tparam T The type of service to find.
206
207 @return A pointer to the service, or `nullptr` if not present.
208 */
209 template<class T>
210 40 T* find_service() const noexcept
211 {
212 40 std::lock_guard<std::mutex> lock(mutex_);
213 40 return static_cast<T*>(find_impl(typeid(T)));
214 40 }
215
216 /** Return a reference to the service of type T, creating it if needed.
217
218 If no service of type T exists, one is created by calling
219 `T(execution_context&)`. If T has a nested `key_type`, the
220 service is also indexed under that type.
221
222 @par Constraints
223 @li `T` must derive from `service`.
224 @li `T` must be constructible from `execution_context&`.
225
226 @par Exception Safety
227 Strong guarantee. If service creation throws, the container
228 is unchanged.
229
230 @par Thread Safety
231 Thread-safe.
232
233 @tparam T The type of service to retrieve or create.
234
235 @return A reference to the service.
236 */
237 template<class T>
238 32 T& use_service()
239 {
240 static_assert(std::is_base_of<service, T>::value,
241 "T must derive from service");
242 static_assert(std::is_constructible<T, execution_context&>::value,
243 "T must be constructible from execution_context&");
244
245 struct impl : factory
246 {
247 17 impl()
248 : factory(
249 typeid(T),
250 get_key<T>::value
251 ? typeid(typename get_key<T>::type)
252 17 : typeid(T))
253 {
254 17 }
255
256 8 service* create(execution_context& ctx) override
257 {
258 8 return new T(ctx);
259 }
260 };
261
262 32 impl f;
263
1/1
✓ Branch 1 taken 17 times.
64 return static_cast<T&>(use_service_impl(f));
264 }
265
266 /** Construct and add a service.
267
268 A new service of type T is constructed using the provided
269 arguments and added to the container. If T has a nested
270 `key_type`, the service is also indexed under that type.
271
272 @par Constraints
273 @li `T` must derive from `service`.
274 @li `T` must be constructible from `execution_context&, Args...`.
275 @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
276
277 @par Exception Safety
278 Strong guarantee. If service creation throws, the container
279 is unchanged.
280
281 @par Thread Safety
282 Thread-safe.
283
284 @throws std::invalid_argument if a service of the same type
285 or `key_type` already exists.
286
287 @tparam T The type of service to create.
288
289 @param args Arguments forwarded to the constructor of T.
290
291 @return A reference to the created service.
292 */
293 template<class T, class... Args>
294 18 T& make_service(Args&&... args)
295 {
296 static_assert(std::is_base_of<service, T>::value,
297 "T must derive from service");
298 if constexpr(get_key<T>::value)
299 {
300 static_assert(
301 std::is_convertible<T&, typename get_key<T>::type&>::value,
302 "T& must be convertible to key_type&");
303 }
304
305 struct impl : factory
306 {
307 std::tuple<Args&&...> args_;
308
309 10 explicit impl(Args&&... a)
310 : factory(
311 typeid(T),
312 get_key<T>::value
313 ? typeid(typename get_key<T>::type)
314 : typeid(T))
315 10 , args_(std::forward<Args>(a)...)
316 {
317 10 }
318
319 7 service* create(execution_context& ctx) override
320 {
321
1/1
✓ Branch 1 taken 1 times.
20 return std::apply([&ctx](auto&&... a) {
322
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)...);
323 21 }, std::move(args_));
324 }
325 };
326
327
2/2
✓ Branch 4 taken 2 times.
✓ Branch 2 taken 6 times.
18 impl f(std::forward<Args>(args)...);
328
1/1
✓ Branch 1 taken 7 times.
31 return static_cast<T&>(make_service_impl(f));
329 }
330
331 protected:
332 /** Shut down all services.
333
334 Calls `shutdown()` on each service in reverse order of creation.
335 After this call, services remain allocated but are in a stopped
336 state. Derived classes should call this in their destructor
337 before any members are destroyed. This function is idempotent;
338 subsequent calls have no effect.
339
340 @par Effects
341 Each service's `shutdown()` member function is invoked once.
342
343 @par Postconditions
344 @li All services are in a stopped state.
345
346 @par Exception Safety
347 No-throw guarantee.
348
349 @par Thread Safety
350 Not thread-safe. Must not be called concurrently with other
351 operations on this execution_context.
352 */
353 void shutdown() noexcept;
354
355 /** Destroy all services.
356
357 Deletes all services in reverse order of creation. Derived
358 classes should call this as the final step of destruction.
359 This function is idempotent; subsequent calls have no effect.
360
361 @par Preconditions
362 @li `shutdown()` has been called.
363
364 @par Effects
365 All services are deleted and removed from the container.
366
367 @par Postconditions
368 @li The service container is empty.
369
370 @par Exception Safety
371 No-throw guarantee.
372
373 @par Thread Safety
374 Not thread-safe. Must not be called concurrently with other
375 operations on this execution_context.
376 */
377 void destroy() noexcept;
378
379 private:
380 struct factory
381 {
382 std::type_index t0;
383 std::type_index t1;
384
385 27 factory(std::type_index t0_, std::type_index t1_)
386 27 : t0(t0_), t1(t1_)
387 {
388 27 }
389
390 virtual service* create(execution_context&) = 0;
391
392 protected:
393 ~factory() = default;
394 };
395
396 service* find_impl(std::type_index ti) const noexcept;
397 service& use_service_impl(factory& f);
398 service& make_service_impl(factory& f);
399
400 #ifdef _MSC_VER
401 # pragma warning(push)
402 # pragma warning(disable: 4251)
403 #endif
404 mutable std::mutex mutex_;
405 #ifdef _MSC_VER
406 # pragma warning(pop)
407 #endif
408 service* head_ = nullptr;
409 bool shutdown_ = false;
410 };
411
412 } // namespace capy
413 } // namespace boost
414
415 #endif
416