include/boost/capy/ex/execution_context.hpp

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