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