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