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