libs/capy/include/boost/capy/ex/execution_context.hpp

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