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