TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : // Copyright (c) 2026 Michael Vandeberg
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/corosio
10 : //
11 :
12 : #ifndef BOOST_COROSIO_IO_CONTEXT_HPP
13 : #define BOOST_COROSIO_IO_CONTEXT_HPP
14 :
15 : #include <boost/corosio/detail/config.hpp>
16 : #include <boost/corosio/detail/platform.hpp>
17 : #include <boost/corosio/detail/scheduler.hpp>
18 : #include <boost/capy/ex/execution_context.hpp>
19 :
20 : #include <chrono>
21 : #include <coroutine>
22 : #include <cstddef>
23 : #include <limits>
24 : #include <thread>
25 :
26 : namespace boost::corosio {
27 :
28 : namespace detail {
29 : struct timer_service_access;
30 : } // namespace detail
31 :
32 : /** An I/O context for running asynchronous operations.
33 :
34 : The io_context provides an execution environment for async
35 : operations. It maintains a queue of pending work items and
36 : processes them when `run()` is called.
37 :
38 : The default and unsigned constructors select the platform's
39 : native backend:
40 : - Windows: IOCP
41 : - Linux: epoll
42 : - BSD/macOS: kqueue
43 : - Other POSIX: select
44 :
45 : The template constructor accepts a backend tag value to
46 : choose a specific backend at compile time:
47 :
48 : @par Example
49 : @code
50 : io_context ioc; // platform default
51 : io_context ioc2(corosio::epoll); // explicit backend
52 : @endcode
53 :
54 : @par Thread Safety
55 : Distinct objects: Safe.@n
56 : Shared objects: Safe, if using a concurrency hint greater
57 : than 1.
58 :
59 : @see epoll_t, select_t, kqueue_t, iocp_t
60 : */
61 : class BOOST_COROSIO_DECL io_context : public capy::execution_context
62 : {
63 : friend struct detail::timer_service_access;
64 :
65 : protected:
66 : detail::scheduler* sched_;
67 :
68 : public:
69 : /** The executor type for this context. */
70 : class executor_type;
71 :
72 : /** Construct with default concurrency and platform backend. */
73 : io_context();
74 :
75 : /** Construct with a concurrency hint and platform backend.
76 :
77 : @param concurrency_hint Hint for the number of threads
78 : that will call `run()`.
79 : */
80 : explicit io_context(unsigned concurrency_hint);
81 :
82 : /** Construct with an explicit backend tag.
83 :
84 : @param backend The backend tag value selecting the I/O
85 : multiplexer (e.g. `corosio::epoll`).
86 : @param concurrency_hint Hint for the number of threads
87 : that will call `run()`.
88 : */
89 : template<class Backend>
90 : requires requires { Backend::construct; }
91 HIT 270 : explicit io_context(
92 : Backend backend,
93 : unsigned concurrency_hint = std::thread::hardware_concurrency())
94 : : capy::execution_context(this)
95 270 : , sched_(nullptr)
96 : {
97 : (void)backend;
98 270 : sched_ = &Backend::construct(*this, concurrency_hint);
99 270 : }
100 :
101 : ~io_context();
102 :
103 : io_context(io_context const&) = delete;
104 : io_context& operator=(io_context const&) = delete;
105 :
106 : /** Return an executor for this context.
107 :
108 : The returned executor can be used to dispatch coroutines
109 : and post work items to this context.
110 :
111 : @return An executor associated with this context.
112 : */
113 : executor_type get_executor() const noexcept;
114 :
115 : /** Signal the context to stop processing.
116 :
117 : This causes `run()` to return as soon as possible. Any pending
118 : work items remain queued.
119 : */
120 1 : void stop()
121 : {
122 1 : sched_->stop();
123 1 : }
124 :
125 : /** Return whether the context has been stopped.
126 :
127 : @return `true` if `stop()` has been called and `restart()`
128 : has not been called since.
129 : */
130 21 : bool stopped() const noexcept
131 : {
132 21 : return sched_->stopped();
133 : }
134 :
135 : /** Restart the context after being stopped.
136 :
137 : This function must be called before `run()` can be called
138 : again after `stop()` has been called.
139 : */
140 89 : void restart()
141 : {
142 89 : sched_->restart();
143 89 : }
144 :
145 : /** Process all pending work items.
146 :
147 : This function blocks until all pending work items have been
148 : executed or `stop()` is called. The context is stopped
149 : when there is no more outstanding work.
150 :
151 : @note The context must be restarted with `restart()` before
152 : calling this function again after it returns.
153 :
154 : @return The number of handlers executed.
155 : */
156 287 : std::size_t run()
157 : {
158 287 : return sched_->run();
159 : }
160 :
161 : /** Process at most one pending work item.
162 :
163 : This function blocks until one work item has been executed
164 : or `stop()` is called. The context is stopped when there
165 : is no more outstanding work.
166 :
167 : @note The context must be restarted with `restart()` before
168 : calling this function again after it returns.
169 :
170 : @return The number of handlers executed (0 or 1).
171 : */
172 2 : std::size_t run_one()
173 : {
174 2 : return sched_->run_one();
175 : }
176 :
177 : /** Process work items for the specified duration.
178 :
179 : This function blocks until work items have been executed for
180 : the specified duration, or `stop()` is called. The context
181 : is stopped when there is no more outstanding work.
182 :
183 : @note The context must be restarted with `restart()` before
184 : calling this function again after it returns.
185 :
186 : @param rel_time The duration for which to process work.
187 :
188 : @return The number of handlers executed.
189 : */
190 : template<class Rep, class Period>
191 8 : std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
192 : {
193 8 : return run_until(std::chrono::steady_clock::now() + rel_time);
194 : }
195 :
196 : /** Process work items until the specified time.
197 :
198 : This function blocks until the specified time is reached
199 : or `stop()` is called. The context is stopped when there
200 : is no more outstanding work.
201 :
202 : @note The context must be restarted with `restart()` before
203 : calling this function again after it returns.
204 :
205 : @param abs_time The time point until which to process work.
206 :
207 : @return The number of handlers executed.
208 : */
209 : template<class Clock, class Duration>
210 : std::size_t
211 8 : run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
212 : {
213 8 : std::size_t n = 0;
214 57 : while (run_one_until(abs_time))
215 49 : if (n != (std::numeric_limits<std::size_t>::max)())
216 49 : ++n;
217 8 : return n;
218 : }
219 :
220 : /** Process at most one work item for the specified duration.
221 :
222 : This function blocks until one work item has been executed,
223 : the specified duration has elapsed, or `stop()` is called.
224 : The context is stopped when there is no more outstanding work.
225 :
226 : @note The context must be restarted with `restart()` before
227 : calling this function again after it returns.
228 :
229 : @param rel_time The duration for which the call may block.
230 :
231 : @return The number of handlers executed (0 or 1).
232 : */
233 : template<class Rep, class Period>
234 2 : std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
235 : {
236 2 : return run_one_until(std::chrono::steady_clock::now() + rel_time);
237 : }
238 :
239 : /** Process at most one work item until the specified time.
240 :
241 : This function blocks until one work item has been executed,
242 : the specified time is reached, or `stop()` is called.
243 : The context is stopped when there is no more outstanding work.
244 :
245 : @note The context must be restarted with `restart()` before
246 : calling this function again after it returns.
247 :
248 : @param abs_time The time point until which the call may block.
249 :
250 : @return The number of handlers executed (0 or 1).
251 : */
252 : template<class Clock, class Duration>
253 : std::size_t
254 61 : run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
255 : {
256 61 : typename Clock::time_point now = Clock::now();
257 61 : while (now < abs_time)
258 : {
259 61 : auto rel_time = abs_time - now;
260 61 : if (rel_time > std::chrono::seconds(1))
261 MIS 0 : rel_time = std::chrono::seconds(1);
262 :
263 HIT 61 : std::size_t s = sched_->wait_one(
264 : static_cast<long>(
265 61 : std::chrono::duration_cast<std::chrono::microseconds>(
266 : rel_time)
267 61 : .count()));
268 :
269 61 : if (s || stopped())
270 61 : return s;
271 :
272 MIS 0 : now = Clock::now();
273 : }
274 0 : return 0;
275 : }
276 :
277 : /** Process all ready work items without blocking.
278 :
279 : This function executes all work items that are ready to run
280 : without blocking for more work. The context is stopped
281 : when there is no more outstanding work.
282 :
283 : @note The context must be restarted with `restart()` before
284 : calling this function again after it returns.
285 :
286 : @return The number of handlers executed.
287 : */
288 HIT 2 : std::size_t poll()
289 : {
290 2 : return sched_->poll();
291 : }
292 :
293 : /** Process at most one ready work item without blocking.
294 :
295 : This function executes at most one work item that is ready
296 : to run without blocking for more work. The context is
297 : stopped when there is no more outstanding work.
298 :
299 : @note The context must be restarted with `restart()` before
300 : calling this function again after it returns.
301 :
302 : @return The number of handlers executed (0 or 1).
303 : */
304 4 : std::size_t poll_one()
305 : {
306 4 : return sched_->poll_one();
307 : }
308 : };
309 :
310 : /** An executor for dispatching work to an I/O context.
311 :
312 : The executor provides the interface for posting work items and
313 : dispatching coroutines to the associated context. It satisfies
314 : the `capy::Executor` concept.
315 :
316 : Executors are lightweight handles that can be copied and compared
317 : for equality. Two executors compare equal if they refer to the
318 : same context.
319 :
320 : @par Thread Safety
321 : Distinct objects: Safe.@n
322 : Shared objects: Safe.
323 : */
324 : class io_context::executor_type
325 : {
326 : io_context* ctx_ = nullptr;
327 :
328 : public:
329 : /** Default constructor.
330 :
331 : Constructs an executor not associated with any context.
332 : */
333 : executor_type() = default;
334 :
335 : /** Construct an executor from a context.
336 :
337 : @param ctx The context to associate with this executor.
338 : */
339 364 : explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
340 :
341 : /** Return a reference to the associated execution context.
342 :
343 : @return Reference to the context.
344 : */
345 1236 : io_context& context() const noexcept
346 : {
347 1236 : return *ctx_;
348 : }
349 :
350 : /** Check if the current thread is running this executor's context.
351 :
352 : @return `true` if `run()` is being called on this thread.
353 : */
354 1256 : bool running_in_this_thread() const noexcept
355 : {
356 1256 : return ctx_->sched_->running_in_this_thread();
357 : }
358 :
359 : /** Informs the executor that work is beginning.
360 :
361 : Must be paired with `on_work_finished()`.
362 : */
363 1261 : void on_work_started() const noexcept
364 : {
365 1261 : ctx_->sched_->work_started();
366 1261 : }
367 :
368 : /** Informs the executor that work has completed.
369 :
370 : @par Preconditions
371 : A preceding call to `on_work_started()` on an equal executor.
372 : */
373 1235 : void on_work_finished() const noexcept
374 : {
375 1235 : ctx_->sched_->work_finished();
376 1235 : }
377 :
378 : /** Dispatch a coroutine handle.
379 :
380 : Returns a handle for symmetric transfer. If called from
381 : within `run()`, returns `h`. Otherwise posts the coroutine
382 : for later execution and returns `std::noop_coroutine()`.
383 :
384 : @param h The coroutine handle to dispatch.
385 :
386 : @return A handle for symmetric transfer or `std::noop_coroutine()`.
387 : */
388 1254 : std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
389 : {
390 1254 : if (running_in_this_thread())
391 819 : return h;
392 435 : ctx_->sched_->post(h);
393 435 : return std::noop_coroutine();
394 : }
395 :
396 : /** Post a coroutine for deferred execution.
397 :
398 : The coroutine will be resumed during a subsequent call to
399 : `run()`.
400 :
401 : @param h The coroutine handle to post.
402 : */
403 10080 : void post(std::coroutine_handle<> h) const
404 : {
405 10080 : ctx_->sched_->post(h);
406 10080 : }
407 :
408 : /** Compare two executors for equality.
409 :
410 : @return `true` if both executors refer to the same context.
411 : */
412 1 : bool operator==(executor_type const& other) const noexcept
413 : {
414 1 : return ctx_ == other.ctx_;
415 : }
416 :
417 : /** Compare two executors for inequality.
418 :
419 : @return `true` if the executors refer to different contexts.
420 : */
421 : bool operator!=(executor_type const& other) const noexcept
422 : {
423 : return ctx_ != other.ctx_;
424 : }
425 : };
426 :
427 : inline io_context::executor_type
428 364 : io_context::get_executor() const noexcept
429 : {
430 364 : return executor_type(const_cast<io_context&>(*this));
431 : }
432 :
433 : } // namespace boost::corosio
434 :
435 : #endif // BOOST_COROSIO_IO_CONTEXT_HPP
|