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 22 : virtual ~service() = default;
144 :
145 : protected:
146 22 : 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 : #ifdef _MSC_VER
167 : # pragma warning(push)
168 : # pragma warning(disable: 4251)
169 : #endif
170 : detail::type_index t0_{detail::type_id<void>()};
171 : detail::type_index t1_{detail::type_id<void>()};
172 : #ifdef _MSC_VER
173 : # pragma warning(pop)
174 : #endif
175 : };
176 :
177 : //------------------------------------------------
178 :
179 : execution_context(execution_context const&) = delete;
180 :
181 : execution_context& operator=(execution_context const&) = delete;
182 :
183 : /** Destructor.
184 :
185 : Calls `shutdown()` then `destroy()` to clean up all services.
186 :
187 : @par Effects
188 : All services are shut down and deleted in reverse order
189 : of creation.
190 :
191 : @par Exception Safety
192 : No-throw guarantee.
193 : */
194 : ~execution_context();
195 :
196 : /** Default constructor.
197 :
198 : @par Exception Safety
199 : Strong guarantee.
200 : */
201 : execution_context();
202 :
203 : /** Return true if a service of type T exists.
204 :
205 : @par Thread Safety
206 : Thread-safe.
207 :
208 : @tparam T The type of service to check.
209 :
210 : @return `true` if the service exists.
211 : */
212 : template<class T>
213 3 : bool has_service() const noexcept
214 : {
215 3 : return find_service<T>() != nullptr;
216 : }
217 :
218 : /** Return a pointer to the service of type T, or nullptr.
219 :
220 : @par Thread Safety
221 : Thread-safe.
222 :
223 : @tparam T The type of service to find.
224 :
225 : @return A pointer to the service, or `nullptr` if not present.
226 : */
227 : template<class T>
228 5 : T* find_service() const noexcept
229 : {
230 5 : std::lock_guard<std::mutex> lock(mutex_);
231 5 : return static_cast<T*>(find_impl(detail::type_id<T>()));
232 5 : }
233 :
234 : /** Return a reference to the service of type T, creating it if needed.
235 :
236 : If no service of type T exists, one is created by calling
237 : `T(execution_context&)`. If T has a nested `key_type`, the
238 : service is also indexed under that type.
239 :
240 : @par Constraints
241 : @li `T` must derive from `service`.
242 : @li `T` must be constructible from `execution_context&`.
243 :
244 : @par Exception Safety
245 : Strong guarantee. If service creation throws, the container
246 : is unchanged.
247 :
248 : @par Thread Safety
249 : Thread-safe.
250 :
251 : @tparam T The type of service to retrieve or create.
252 :
253 : @return A reference to the service.
254 : */
255 : template<class T>
256 27 : T& use_service()
257 : {
258 : static_assert(std::is_base_of<service, T>::value,
259 : "T must derive from service");
260 : static_assert(std::is_constructible<T, execution_context&>::value,
261 : "T must be constructible from execution_context&");
262 :
263 : struct impl : factory
264 : {
265 27 : impl()
266 : : factory(
267 : detail::type_id<T>(),
268 : get_key<T>::value
269 : ? detail::type_id<typename get_key<T>::type>()
270 27 : : detail::type_id<T>())
271 : {
272 27 : }
273 :
274 21 : service* create(execution_context& ctx) override
275 : {
276 21 : return new T(ctx);
277 : }
278 : };
279 :
280 27 : impl f;
281 54 : return static_cast<T&>(use_service_impl(f));
282 : }
283 :
284 : /** Construct and add a service.
285 :
286 : A new service of type T is constructed using the provided
287 : arguments and added to the container. If T has a nested
288 : `key_type`, the service is also indexed under that type.
289 :
290 : @par Constraints
291 : @li `T` must derive from `service`.
292 : @li `T` must be constructible from `execution_context&, Args...`.
293 : @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
294 :
295 : @par Exception Safety
296 : Strong guarantee. If service creation throws, the container
297 : is unchanged.
298 :
299 : @par Thread Safety
300 : Thread-safe.
301 :
302 : @throws std::invalid_argument if a service of the same type
303 : or `key_type` already exists.
304 :
305 : @tparam T The type of service to create.
306 :
307 : @param args Arguments forwarded to the constructor of T.
308 :
309 : @return A reference to the created service.
310 : */
311 : template<class T, class... Args>
312 2 : T& make_service(Args&&... args)
313 : {
314 : static_assert(std::is_base_of<service, T>::value,
315 : "T must derive from service");
316 : if constexpr(get_key<T>::value)
317 : {
318 : static_assert(
319 : std::is_convertible<T&, typename get_key<T>::type&>::value,
320 : "T& must be convertible to key_type&");
321 : }
322 :
323 : struct impl : factory
324 : {
325 : std::tuple<Args&&...> args_;
326 :
327 2 : explicit impl(Args&&... a)
328 : : factory(
329 : detail::type_id<T>(),
330 : get_key<T>::value
331 : ? detail::type_id<typename get_key<T>::type>()
332 : : detail::type_id<T>())
333 2 : , args_(std::forward<Args>(a)...)
334 : {
335 2 : }
336 :
337 1 : service* create(execution_context& ctx) override
338 : {
339 2 : return std::apply([&ctx](auto&&... a) {
340 1 : return new T(ctx, std::forward<decltype(a)>(a)...);
341 3 : }, std::move(args_));
342 : }
343 : };
344 :
345 2 : impl f(std::forward<Args>(args)...);
346 3 : return static_cast<T&>(make_service_impl(f));
347 : }
348 :
349 : //------------------------------------------------
350 :
351 : /** Return the memory resource used for coroutine frame allocation.
352 :
353 : The returned pointer is valid for the lifetime of this context.
354 : By default, this returns a pointer to the recycling memory
355 : resource which pools frame allocations for reuse.
356 :
357 : @return Pointer to the frame allocator.
358 :
359 : @see set_frame_allocator
360 : */
361 : std::pmr::memory_resource*
362 2015 : get_frame_allocator() const noexcept
363 : {
364 2015 : return frame_alloc_;
365 : }
366 :
367 : /** Set the memory resource used for coroutine frame allocation.
368 :
369 : The caller is responsible for ensuring the memory resource
370 : remains valid for the lifetime of all coroutines launched
371 : using this context's executor.
372 :
373 : @par Thread Safety
374 : Not thread-safe. Must not be called while any thread may
375 : be referencing this execution context or its executor.
376 :
377 : @param mr Pointer to the memory resource.
378 :
379 : @see get_frame_allocator
380 : */
381 : void
382 : set_frame_allocator(std::pmr::memory_resource* mr) noexcept
383 : {
384 : owned_.reset();
385 : frame_alloc_ = mr;
386 : }
387 :
388 : /** Set the frame allocator from a standard Allocator.
389 :
390 : The allocator is wrapped in an internal memory resource
391 : adapter owned by this context. The wrapper remains valid
392 : for the lifetime of this context or until a subsequent
393 : call to set_frame_allocator.
394 :
395 : @par Thread Safety
396 : Not thread-safe. Must not be called while any thread may
397 : be referencing this execution context or its executor.
398 :
399 : @tparam Allocator The allocator type satisfying the
400 : standard Allocator requirements.
401 :
402 : @param a The allocator to use.
403 :
404 : @see get_frame_allocator
405 : */
406 : template<class Allocator>
407 : requires (!std::is_pointer_v<Allocator>)
408 : void
409 61 : set_frame_allocator(Allocator const& a)
410 : {
411 : static_assert(
412 : requires { typename std::allocator_traits<Allocator>::value_type; },
413 : "Allocator must satisfy allocator requirements");
414 : static_assert(
415 : std::is_copy_constructible_v<Allocator>,
416 : "Allocator must be copy constructible");
417 :
418 61 : auto p = std::make_shared<
419 : detail::frame_memory_resource<Allocator>>(a);
420 61 : frame_alloc_ = p.get();
421 61 : owned_ = std::move(p);
422 61 : }
423 :
424 : /** Return a pointer to this context if it matches the
425 : requested type.
426 :
427 : Performs a type check and downcasts `this` when the
428 : types match, or returns `nullptr` otherwise. Analogous
429 : to `std::any_cast< ExecutionContext >( &a )`.
430 :
431 : @tparam ExecutionContext The derived context type to
432 : retrieve.
433 :
434 : @return A pointer to this context as the requested
435 : type, or `nullptr` if the type does not match.
436 : */
437 : template< typename ExecutionContext >
438 : const ExecutionContext* target() const
439 : {
440 : if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
441 : return static_cast< ExecutionContext const* >( this );
442 : return nullptr;
443 : }
444 :
445 : /// @copydoc target() const
446 : template< typename ExecutionContext >
447 : ExecutionContext* target()
448 : {
449 : if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
450 : return static_cast< ExecutionContext* >( this );
451 : return nullptr;
452 : }
453 :
454 : protected:
455 : /** Shut down all services.
456 :
457 : Calls `shutdown()` on each service in reverse order of creation.
458 : After this call, services remain allocated but are in a stopped
459 : state. Derived classes should call this in their destructor
460 : before any members are destroyed. This function is idempotent;
461 : subsequent calls have no effect.
462 :
463 : @par Effects
464 : Each service's `shutdown()` member function is invoked once.
465 :
466 : @par Postconditions
467 : @li All services are in a stopped state.
468 :
469 : @par Exception Safety
470 : No-throw guarantee.
471 :
472 : @par Thread Safety
473 : Not thread-safe. Must not be called concurrently with other
474 : operations on this execution_context.
475 : */
476 : void shutdown() noexcept;
477 :
478 : /** Destroy all services.
479 :
480 : Deletes all services in reverse order of creation. Derived
481 : classes should call this as the final step of destruction.
482 : This function is idempotent; subsequent calls have no effect.
483 :
484 : @par Preconditions
485 : @li `shutdown()` has been called.
486 :
487 : @par Effects
488 : All services are deleted and removed from the container.
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 : #ifdef _MSC_VER
507 : # pragma warning(push)
508 : # pragma warning(disable: 4251)
509 : #endif
510 : // warning C4251: 'std::type_index' needs to have dll-interface
511 : detail::type_index t0;
512 : detail::type_index t1;
513 : #ifdef _MSC_VER
514 : # pragma warning(pop)
515 : #endif
516 :
517 29 : factory(
518 : detail::type_info const& t0_,
519 : detail::type_info const& t1_)
520 29 : : t0(t0_), t1(t1_)
521 : {
522 29 : }
523 :
524 : virtual service* create(execution_context&) = 0;
525 :
526 : protected:
527 : ~factory() = default;
528 : };
529 :
530 : service* find_impl(detail::type_index ti) const noexcept;
531 : service& use_service_impl(factory& f);
532 : service& make_service_impl(factory& f);
533 :
534 : #ifdef _MSC_VER
535 : # pragma warning(push)
536 : # pragma warning(disable: 4251)
537 : #endif
538 : // warning C4251: 'std::type_index' needs to have dll-interface
539 : mutable std::mutex mutex_;
540 : std::shared_ptr<void> owned_;
541 : #ifdef _MSC_VER
542 : # pragma warning(pop)
543 : #endif
544 : std::pmr::memory_resource* frame_alloc_ = nullptr;
545 : service* head_ = nullptr;
546 : bool shutdown_ = false;
547 : };
548 :
549 : template< typename Derived >
550 6 : execution_context::
551 : execution_context( Derived* ) noexcept
552 6 : : execution_context()
553 : {
554 6 : ti_ = &detail::type_id< Derived >();
555 6 : }
556 :
557 : } // namespace capy
558 : } // namespace boost
559 :
560 : #endif
|