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/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 HIT 69 : virtual ~service() = default;
157 :
158 : protected:
159 69 : 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 16 : bool has_service() const noexcept
223 : {
224 16 : 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 25 : T* find_service() const noexcept
238 : {
239 25 : std::lock_guard<std::mutex> lock(mutex_);
240 25 : return static_cast<T*>(find_impl(detail::type_id<T>()));
241 25 : }
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 11492 : 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 11492 : impl()
275 : : factory(
276 : detail::type_id<T>(),
277 : get_key<T>::value
278 : ? detail::type_id<typename get_key<T>::type>()
279 11492 : : detail::type_id<T>())
280 : {
281 11492 : }
282 :
283 60 : service* create(execution_context& ctx) override
284 : {
285 60 : return new T(ctx);
286 : }
287 : };
288 :
289 11492 : impl f;
290 22984 : 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 12 : 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 12 : 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 12 : , args_(std::forward<Args>(a)...)
343 : {
344 12 : }
345 :
346 9 : service* create(execution_context& ctx) override
347 : {
348 26 : return std::apply([&ctx](auto&&... a) {
349 11 : return new T(ctx, std::forward<decltype(a)>(a)...);
350 27 : }, std::move(args_));
351 : }
352 : };
353 :
354 12 : impl f(std::forward<Args>(args)...);
355 20 : 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 3327 : get_frame_allocator() const noexcept
372 : {
373 3327 : 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 1 : set_frame_allocator(std::pmr::memory_resource* mr) noexcept
392 : {
393 1 : owned_.reset();
394 1 : frame_alloc_ = mr;
395 1 : }
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 169 : 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 169 : auto p = std::make_shared<
428 : detail::frame_memory_resource<Allocator>>(a);
429 169 : frame_alloc_ = p.get();
430 169 : owned_ = std::move(p);
431 169 : }
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 2 : const ExecutionContext* target() const
448 : {
449 2 : if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
450 1 : return static_cast< ExecutionContext const* >( this );
451 1 : return nullptr;
452 : }
453 :
454 : /// @copydoc target() const
455 : template< typename ExecutionContext >
456 2 : ExecutionContext* target()
457 : {
458 2 : if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
459 1 : return static_cast< ExecutionContext* >( this );
460 1 : 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 11504 : factory(
523 : detail::type_info const& t0_,
524 : detail::type_info const& t1_)
525 11504 : : t0(t0_), t1(t1_)
526 : {
527 11504 : }
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 29 : execution_context::
552 : execution_context( Derived* ) noexcept
553 29 : : execution_context()
554 : {
555 29 : ti_ = &detail::type_id< Derived >();
556 29 : }
557 :
558 : } // namespace capy
559 : } // namespace boost
560 :
561 : #endif
|