TLA Line data 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 HIT 66 : virtual ~service() = default;
146 :
147 : protected:
148 66 : 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 14 : bool has_service() const noexcept
212 : {
213 14 : 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 27 : T* find_service() const noexcept
227 : {
228 27 : auto id = detail::service_slot<T>();
229 27 : if(id < max_service_slots)
230 : {
231 27 : auto* p = slots_[id].load(
232 : std::memory_order_acquire);
233 27 : if(p)
234 23 : return static_cast<T*>(p);
235 : }
236 4 : std::lock_guard<std::mutex> lock(mutex_);
237 4 : return static_cast<T*>(find_impl(detail::type_id<T>()));
238 4 : }
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 94 : 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 94 : auto id = detail::service_slot<T>();
278 94 : if(id < max_service_slots)
279 : {
280 94 : auto* p = slots_[id].load(
281 : std::memory_order_acquire);
282 94 : if(p)
283 37 : return static_cast<T&>(*p);
284 : }
285 : }
286 :
287 : struct impl : factory
288 : {
289 57 : 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 57 : : detail::service_slot<T>())
299 : {
300 57 : }
301 :
302 57 : service* create(execution_context& ctx) override
303 : {
304 57 : return new T(ctx);
305 : }
306 : };
307 :
308 57 : impl f;
309 57 : 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 12 : 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 12 : 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 12 : , args_(std::forward<Args>(a)...)
366 : {
367 12 : }
368 :
369 9 : service* create(execution_context& ctx) override
370 : {
371 26 : return std::apply([&ctx](auto&&... a) {
372 11 : return new T(ctx, std::forward<decltype(a)>(a)...);
373 27 : }, std::move(args_));
374 : }
375 : };
376 :
377 12 : impl f(std::forward<Args>(args)...);
378 21 : 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 3277 : get_frame_allocator() const noexcept
395 : {
396 3277 : 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 1 : set_frame_allocator(std::pmr::memory_resource* mr) noexcept
415 : {
416 1 : owned_.reset();
417 1 : frame_alloc_ = mr;
418 1 : }
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 158 : 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 158 : auto p = std::make_shared<
451 : detail::frame_memory_resource<Allocator>>(a);
452 158 : frame_alloc_ = p.get();
453 158 : owned_ = std::move(p);
454 158 : }
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 1 : const ExecutionContext* target() const
471 : {
472 1 : if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
473 1 : return static_cast< ExecutionContext const* >( this );
474 MIS 0 : return nullptr;
475 : }
476 :
477 : /// @copydoc target() const
478 : template< typename ExecutionContext >
479 HIT 2 : ExecutionContext* target()
480 : {
481 2 : if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
482 1 : return static_cast< ExecutionContext* >( this );
483 1 : 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 69 : factory(
548 : detail::type_info const& t0_,
549 : detail::type_info const& t1_,
550 : std::size_t s0,
551 : std::size_t s1)
552 69 : : t0(t0_), t1(t1_)
553 69 : , slot0(s0), slot1(s1)
554 : {
555 69 : }
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 31 : execution_context::
586 : execution_context( Derived* ) noexcept
587 31 : : execution_context()
588 : {
589 31 : ti_ = &detail::type_id< Derived >();
590 31 : }
591 :
592 : } // namespace capy
593 : } // namespace boost
594 :
595 : #endif
|