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