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 service
132 : {
133 : public:
134 15 : virtual ~service() = default;
135 :
136 : protected:
137 15 : service() = default;
138 :
139 : /** Called when the owning execution context shuts down.
140 :
141 : Implementations should release resources and cancel any
142 : outstanding asynchronous operations. This function must
143 : not block and must not throw exceptions. Services are
144 : shut down in reverse order of creation.
145 :
146 : @par Exception Safety
147 : No-throw guarantee.
148 : */
149 : virtual void shutdown() = 0;
150 :
151 : private:
152 : friend class execution_context;
153 :
154 : service* next_ = nullptr;
155 : std::type_index t0_ = typeid(void);
156 : std::type_index t1_ = typeid(void);
157 : };
158 :
159 : //------------------------------------------------
160 :
161 : execution_context(execution_context const&) = delete;
162 :
163 : execution_context& operator=(execution_context const&) = delete;
164 :
165 : /** Destructor.
166 :
167 : Calls `shutdown()` then `destroy()` to clean up all services.
168 :
169 : @par Effects
170 : All services are shut down and deleted in reverse order
171 : of creation.
172 :
173 : @par Exception Safety
174 : No-throw guarantee.
175 : */
176 : ~execution_context();
177 :
178 : /** Default constructor.
179 :
180 : @par Exception Safety
181 : Strong guarantee.
182 : */
183 : execution_context();
184 :
185 : /** Return true if a service of type T exists.
186 :
187 : @par Thread Safety
188 : Thread-safe.
189 :
190 : @tparam T The type of service to check.
191 :
192 : @return `true` if the service exists.
193 : */
194 : template<class T>
195 13 : bool has_service() const noexcept
196 : {
197 13 : return find_service<T>() != nullptr;
198 : }
199 :
200 : /** Return a pointer to the service of type T, or nullptr.
201 :
202 : @par Thread Safety
203 : Thread-safe.
204 :
205 : @tparam T The type of service to find.
206 :
207 : @return A pointer to the service, or `nullptr` if not present.
208 : */
209 : template<class T>
210 22 : T* find_service() const noexcept
211 : {
212 22 : std::lock_guard<std::mutex> lock(mutex_);
213 22 : return static_cast<T*>(find_impl(typeid(T)));
214 22 : }
215 :
216 : /** Return a reference to the service of type T, creating it if needed.
217 :
218 : If no service of type T exists, one is created by calling
219 : `T(execution_context&)`. If T has a nested `key_type`, the
220 : service is also indexed under that type.
221 :
222 : @par Constraints
223 : @li `T` must derive from `service`.
224 : @li `T` must be constructible from `execution_context&`.
225 :
226 : @par Exception Safety
227 : Strong guarantee. If service creation throws, the container
228 : is unchanged.
229 :
230 : @par Thread Safety
231 : Thread-safe.
232 :
233 : @tparam T The type of service to retrieve or create.
234 :
235 : @return A reference to the service.
236 : */
237 : template<class T>
238 17 : T& use_service()
239 : {
240 : static_assert(std::is_base_of<service, T>::value,
241 : "T must derive from service");
242 : static_assert(std::is_constructible<T, execution_context&>::value,
243 : "T must be constructible from execution_context&");
244 :
245 : struct impl : factory
246 : {
247 17 : impl()
248 : : factory(
249 : typeid(T),
250 : get_key<T>::value
251 : ? typeid(typename get_key<T>::type)
252 17 : : typeid(T))
253 : {
254 17 : }
255 :
256 8 : service* create(execution_context& ctx) override
257 : {
258 8 : return new T(ctx);
259 : }
260 : };
261 :
262 17 : impl f;
263 34 : return static_cast<T&>(use_service_impl(f));
264 : }
265 :
266 : /** Construct and add a service.
267 :
268 : A new service of type T is constructed using the provided
269 : arguments and added to the container. If T has a nested
270 : `key_type`, the service is also indexed under that type.
271 :
272 : @par Constraints
273 : @li `T` must derive from `service`.
274 : @li `T` must be constructible from `execution_context&, Args...`.
275 : @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
276 :
277 : @par Exception Safety
278 : Strong guarantee. If service creation throws, the container
279 : is unchanged.
280 :
281 : @par Thread Safety
282 : Thread-safe.
283 :
284 : @throws std::invalid_argument if a service of the same type
285 : or `key_type` already exists.
286 :
287 : @tparam T The type of service to create.
288 :
289 : @param args Arguments forwarded to the constructor of T.
290 :
291 : @return A reference to the created service.
292 : */
293 : template<class T, class... Args>
294 10 : T& make_service(Args&&... args)
295 : {
296 : static_assert(std::is_base_of<service, T>::value,
297 : "T must derive from service");
298 : if constexpr(get_key<T>::value)
299 : {
300 : static_assert(
301 : std::is_convertible<T&, typename get_key<T>::type&>::value,
302 : "T& must be convertible to key_type&");
303 : }
304 :
305 : struct impl : factory
306 : {
307 : std::tuple<Args&&...> args_;
308 :
309 10 : explicit impl(Args&&... a)
310 : : factory(
311 : typeid(T),
312 : get_key<T>::value
313 : ? typeid(typename get_key<T>::type)
314 : : typeid(T))
315 10 : , args_(std::forward<Args>(a)...)
316 : {
317 10 : }
318 :
319 7 : service* create(execution_context& ctx) override
320 : {
321 20 : return std::apply([&ctx](auto&&... a) {
322 9 : return new T(ctx, std::forward<decltype(a)>(a)...);
323 21 : }, std::move(args_));
324 : }
325 : };
326 :
327 10 : impl f(std::forward<Args>(args)...);
328 17 : return static_cast<T&>(make_service_impl(f));
329 : }
330 :
331 : protected:
332 : /** Shut down all services.
333 :
334 : Calls `shutdown()` on each service in reverse order of creation.
335 : After this call, services remain allocated but are in a stopped
336 : state. Derived classes should call this in their destructor
337 : before any members are destroyed. This function is idempotent;
338 : subsequent calls have no effect.
339 :
340 : @par Effects
341 : Each service's `shutdown()` member function is invoked once.
342 :
343 : @par Postconditions
344 : @li All services are in a stopped state.
345 :
346 : @par Exception Safety
347 : No-throw guarantee.
348 :
349 : @par Thread Safety
350 : Not thread-safe. Must not be called concurrently with other
351 : operations on this execution_context.
352 : */
353 : void shutdown() noexcept;
354 :
355 : /** Destroy all services.
356 :
357 : Deletes all services in reverse order of creation. Derived
358 : classes should call this as the final step of destruction.
359 : This function is idempotent; subsequent calls have no effect.
360 :
361 : @par Preconditions
362 : @li `shutdown()` has been called.
363 :
364 : @par Effects
365 : All services are deleted and removed from the container.
366 :
367 : @par Postconditions
368 : @li The service container is empty.
369 :
370 : @par Exception Safety
371 : No-throw guarantee.
372 :
373 : @par Thread Safety
374 : Not thread-safe. Must not be called concurrently with other
375 : operations on this execution_context.
376 : */
377 : void destroy() noexcept;
378 :
379 : private:
380 : struct factory
381 : {
382 : std::type_index t0;
383 : std::type_index t1;
384 :
385 27 : factory(std::type_index t0_, std::type_index t1_)
386 27 : : t0(t0_), t1(t1_)
387 : {
388 27 : }
389 :
390 : virtual service* create(execution_context&) = 0;
391 :
392 : protected:
393 : ~factory() = default;
394 : };
395 :
396 : service* find_impl(std::type_index ti) const noexcept;
397 : service& use_service_impl(factory& f);
398 : service& make_service_impl(factory& f);
399 :
400 : #ifdef _MSC_VER
401 : # pragma warning(push)
402 : # pragma warning(disable: 4251)
403 : #endif
404 : mutable std::mutex mutex_;
405 : #ifdef _MSC_VER
406 : # pragma warning(pop)
407 : #endif
408 : service* head_ = nullptr;
409 : bool shutdown_ = false;
410 : };
411 :
412 : } // namespace capy
413 : } // namespace boost
414 :
415 : #endif
|