include/boost/capy/ex/execution_context.hpp

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