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/concept/executor.hpp>
15 : #include <concepts>
16 : #include <mutex>
17 : #include <tuple>
18 : #include <type_traits>
19 : #include <typeindex>
20 : #include <utility>
21 :
22 : namespace boost {
23 : namespace capy {
24 :
25 : /** Base class for I/O object containers providing service management.
26 :
27 : An execution context represents a place where function objects are
28 : executed. It provides a service registry where polymorphic services
29 : can be stored and retrieved by type. Each service type may be stored
30 : at most once. Services may specify a nested `key_type` to enable
31 : lookup by a base class type.
32 :
33 : Derived classes such as `io_context` extend this to provide
34 : execution facilities like event loops and thread pools. Derived
35 : class destructors must call `shutdown()` and `destroy()` to ensure
36 : proper service cleanup before member destruction.
37 :
38 : @par Service Lifecycle
39 : Services are created on first use via `use_service()` or explicitly
40 : via `make_service()`. During destruction, `shutdown()` is called on
41 : each service in reverse order of creation, then `destroy()` deletes
42 : them. Both functions are idempotent.
43 :
44 : @par Thread Safety
45 : Service registration and lookup functions are thread-safe.
46 : The `shutdown()` and `destroy()` functions are not thread-safe
47 : and must only be called during destruction.
48 :
49 : @par Example
50 : @code
51 : struct file_service : execution_context::service
52 : {
53 : protected:
54 : void shutdown() override {}
55 : };
56 :
57 : struct posix_file_service : file_service
58 : {
59 : using key_type = file_service;
60 :
61 : explicit posix_file_service(execution_context&) {}
62 : };
63 :
64 : class io_context : public execution_context
65 : {
66 : public:
67 : ~io_context()
68 : {
69 : shutdown();
70 : destroy();
71 : }
72 : };
73 :
74 : io_context ctx;
75 : ctx.make_service<posix_file_service>();
76 : ctx.find_service<file_service>(); // returns posix_file_service*
77 : ctx.find_service<posix_file_service>(); // also works
78 : @endcode
79 :
80 : @see service, is_execution_context
81 : */
82 : class BOOST_CAPY_DECL
83 : execution_context
84 : {
85 : template<class T, class = void>
86 : struct get_key : std::false_type
87 : {};
88 :
89 : template<class T>
90 : struct get_key<T, std::void_t<typename T::key_type>> : std::true_type
91 : {
92 : using type = typename T::key_type;
93 : };
94 :
95 : public:
96 : //------------------------------------------------
97 :
98 : /** Abstract base class for services owned by an execution context.
99 :
100 : Services provide extensible functionality to an execution context.
101 : Each service type can be registered at most once. Services are
102 : created via `use_service()` or `make_service()` and are owned by
103 : the execution context for their lifetime.
104 :
105 : Derived classes must implement the pure virtual `shutdown()` member
106 : function, which is called when the owning execution context is
107 : being destroyed. The `shutdown()` function should release resources
108 : and cancel outstanding operations without blocking.
109 :
110 : @par Deriving from service
111 : @li Implement `shutdown()` to perform cleanup.
112 : @li Accept `execution_context&` as the first constructor parameter.
113 : @li Optionally define `key_type` to enable base-class lookup.
114 :
115 : @par Example
116 : @code
117 : struct my_service : execution_context::service
118 : {
119 : explicit my_service(execution_context&) {}
120 :
121 : protected:
122 : void shutdown() override
123 : {
124 : // Cancel pending operations, release resources
125 : }
126 : };
127 : @endcode
128 :
129 : @see execution_context
130 : */
131 : class BOOST_CAPY_DECL
132 : service
133 : {
134 : public:
135 33 : virtual ~service() = default;
136 :
137 : protected:
138 33 : service() = default;
139 :
140 : /** Called when the owning execution context shuts down.
141 :
142 : Implementations should release resources and cancel any
143 : outstanding asynchronous operations. This function must
144 : not block and must not throw exceptions. Services are
145 : shut down in reverse order of creation.
146 :
147 : @par Exception Safety
148 : No-throw guarantee.
149 : */
150 : virtual void shutdown() = 0;
151 :
152 : private:
153 : friend class execution_context;
154 :
155 : service* next_ = nullptr;
156 : #ifdef _MSC_VER
157 : # pragma warning(push)
158 : # pragma warning(disable: 4251)
159 : #endif
160 : std::type_index t0_ = typeid(void);
161 : std::type_index t1_ = typeid(void);
162 : #ifdef _MSC_VER
163 : # pragma warning(pop)
164 : #endif
165 : };
166 :
167 : //------------------------------------------------
168 :
169 : execution_context(execution_context const&) = delete;
170 :
171 : execution_context& operator=(execution_context const&) = delete;
172 :
173 : /** Destructor.
174 :
175 : Calls `shutdown()` then `destroy()` to clean up all services.
176 :
177 : @par Effects
178 : All services are shut down and deleted in reverse order
179 : of creation.
180 :
181 : @par Exception Safety
182 : No-throw guarantee.
183 : */
184 : ~execution_context();
185 :
186 : /** Default constructor.
187 :
188 : @par Exception Safety
189 : Strong guarantee.
190 : */
191 : execution_context();
192 :
193 : /** Return true if a service of type T exists.
194 :
195 : @par Thread Safety
196 : Thread-safe.
197 :
198 : @tparam T The type of service to check.
199 :
200 : @return `true` if the service exists.
201 : */
202 : template<class T>
203 13 : bool has_service() const noexcept
204 : {
205 13 : return find_service<T>() != nullptr;
206 : }
207 :
208 : /** Return a pointer to the service of type T, or nullptr.
209 :
210 : @par Thread Safety
211 : Thread-safe.
212 :
213 : @tparam T The type of service to find.
214 :
215 : @return A pointer to the service, or `nullptr` if not present.
216 : */
217 : template<class T>
218 22 : T* find_service() const noexcept
219 : {
220 22 : std::lock_guard<std::mutex> lock(mutex_);
221 22 : return static_cast<T*>(find_impl(typeid(T)));
222 22 : }
223 :
224 : /** Return a reference to the service of type T, creating it if needed.
225 :
226 : If no service of type T exists, one is created by calling
227 : `T(execution_context&)`. If T has a nested `key_type`, the
228 : service is also indexed under that type.
229 :
230 : @par Constraints
231 : @li `T` must derive from `service`.
232 : @li `T` must be constructible from `execution_context&`.
233 :
234 : @par Exception Safety
235 : Strong guarantee. If service creation throws, the container
236 : is unchanged.
237 :
238 : @par Thread Safety
239 : Thread-safe.
240 :
241 : @tparam T The type of service to retrieve or create.
242 :
243 : @return A reference to the service.
244 : */
245 : template<class T>
246 42 : T& use_service()
247 : {
248 : static_assert(std::is_base_of<service, T>::value,
249 : "T must derive from service");
250 : static_assert(std::is_constructible<T, execution_context&>::value,
251 : "T must be constructible from execution_context&");
252 :
253 : struct impl : factory
254 : {
255 42 : impl()
256 : : factory(
257 : typeid(T),
258 : get_key<T>::value
259 : ? typeid(typename get_key<T>::type)
260 42 : : typeid(T))
261 : {
262 42 : }
263 :
264 26 : service* create(execution_context& ctx) override
265 : {
266 26 : return new T(ctx);
267 : }
268 : };
269 :
270 42 : impl f;
271 84 : return static_cast<T&>(use_service_impl(f));
272 : }
273 :
274 : /** Construct and add a service.
275 :
276 : A new service of type T is constructed using the provided
277 : arguments and added to the container. If T has a nested
278 : `key_type`, the service is also indexed under that type.
279 :
280 : @par Constraints
281 : @li `T` must derive from `service`.
282 : @li `T` must be constructible from `execution_context&, Args...`.
283 : @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
284 :
285 : @par Exception Safety
286 : Strong guarantee. If service creation throws, the container
287 : is unchanged.
288 :
289 : @par Thread Safety
290 : Thread-safe.
291 :
292 : @throws std::invalid_argument if a service of the same type
293 : or `key_type` already exists.
294 :
295 : @tparam T The type of service to create.
296 :
297 : @param args Arguments forwarded to the constructor of T.
298 :
299 : @return A reference to the created service.
300 : */
301 : template<class T, class... Args>
302 10 : T& make_service(Args&&... args)
303 : {
304 : static_assert(std::is_base_of<service, T>::value,
305 : "T must derive from service");
306 : if constexpr(get_key<T>::value)
307 : {
308 : static_assert(
309 : std::is_convertible<T&, typename get_key<T>::type&>::value,
310 : "T& must be convertible to key_type&");
311 : }
312 :
313 : struct impl : factory
314 : {
315 : std::tuple<Args&&...> args_;
316 :
317 10 : explicit impl(Args&&... a)
318 : : factory(
319 : typeid(T),
320 : get_key<T>::value
321 : ? typeid(typename get_key<T>::type)
322 : : typeid(T))
323 10 : , args_(std::forward<Args>(a)...)
324 : {
325 10 : }
326 :
327 7 : service* create(execution_context& ctx) override
328 : {
329 20 : return std::apply([&ctx](auto&&... a) {
330 9 : return new T(ctx, std::forward<decltype(a)>(a)...);
331 21 : }, std::move(args_));
332 : }
333 : };
334 :
335 10 : impl f(std::forward<Args>(args)...);
336 17 : return static_cast<T&>(make_service_impl(f));
337 : }
338 :
339 : protected:
340 : /** Shut down all services.
341 :
342 : Calls `shutdown()` on each service in reverse order of creation.
343 : After this call, services remain allocated but are in a stopped
344 : state. Derived classes should call this in their destructor
345 : before any members are destroyed. This function is idempotent;
346 : subsequent calls have no effect.
347 :
348 : @par Effects
349 : Each service's `shutdown()` member function is invoked once.
350 :
351 : @par Postconditions
352 : @li All services are in a stopped state.
353 :
354 : @par Exception Safety
355 : No-throw guarantee.
356 :
357 : @par Thread Safety
358 : Not thread-safe. Must not be called concurrently with other
359 : operations on this execution_context.
360 : */
361 : void shutdown() noexcept;
362 :
363 : /** Destroy all services.
364 :
365 : Deletes all services in reverse order of creation. Derived
366 : classes should call this as the final step of destruction.
367 : This function is idempotent; subsequent calls have no effect.
368 :
369 : @par Preconditions
370 : @li `shutdown()` has been called.
371 :
372 : @par Effects
373 : All services are deleted and removed from the container.
374 :
375 : @par Postconditions
376 : @li The service container is empty.
377 :
378 : @par Exception Safety
379 : No-throw guarantee.
380 :
381 : @par Thread Safety
382 : Not thread-safe. Must not be called concurrently with other
383 : operations on this execution_context.
384 : */
385 : void destroy() noexcept;
386 :
387 : private:
388 : struct BOOST_CAPY_DECL
389 : factory
390 : {
391 : #ifdef _MSC_VER
392 : # pragma warning(push)
393 : # pragma warning(disable: 4251)
394 : #endif
395 : std::type_index t0;
396 : std::type_index t1;
397 : #ifdef _MSC_VER
398 : # pragma warning(pop)
399 : #endif
400 :
401 52 : factory(std::type_index t0_, std::type_index t1_)
402 52 : : t0(t0_), t1(t1_)
403 : {
404 52 : }
405 :
406 : virtual service* create(execution_context&) = 0;
407 :
408 : protected:
409 : ~factory() = default;
410 : };
411 :
412 : service* find_impl(std::type_index ti) const noexcept;
413 : service& use_service_impl(factory& f);
414 : service& make_service_impl(factory& f);
415 :
416 : #ifdef _MSC_VER
417 : # pragma warning(push)
418 : # pragma warning(disable: 4251)
419 : #endif
420 : mutable std::mutex mutex_;
421 : #ifdef _MSC_VER
422 : # pragma warning(pop)
423 : #endif
424 : service* head_ = nullptr;
425 : bool shutdown_ = false;
426 : };
427 :
428 : } // namespace capy
429 : } // namespace boost
430 :
431 : #endif
|