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