include/boost/corosio/io_context.hpp

95.5% Lines (63/66) 100.0% Functions (22/22)
Line TLA Hits 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 336x explicit io_context(
92 Backend backend,
93 unsigned concurrency_hint = std::thread::hardware_concurrency())
94 : capy::execution_context(this)
95 336x , sched_(nullptr)
96 {
97 (void)backend;
98 336x sched_ = &Backend::construct(*this, concurrency_hint);
99 336x }
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 3x void stop()
121 {
122 3x sched_->stop();
123 3x }
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 21x bool stopped() const noexcept
131 {
132 21x 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 91x void restart()
141 {
142 91x sched_->restart();
143 91x }
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 331x std::size_t run()
157 {
158 331x 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 2x std::size_t run_one()
173 {
174 2x 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 8x std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
192 {
193 8x 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 8x run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
212 {
213 8x std::size_t n = 0;
214 57x while (run_one_until(abs_time))
215 49x if (n != (std::numeric_limits<std::size_t>::max)())
216 49x ++n;
217 8x 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 2x std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
235 {
236 2x 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 61x run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
255 {
256 61x typename Clock::time_point now = Clock::now();
257 61x while (now < abs_time)
258 {
259 61x auto rel_time = abs_time - now;
260 61x if (rel_time > std::chrono::seconds(1))
261 rel_time = std::chrono::seconds(1);
262
263 61x std::size_t s = sched_->wait_one(
264 static_cast<long>(
265 61x std::chrono::duration_cast<std::chrono::microseconds>(
266 rel_time)
267 61x .count()));
268
269 61x if (s || stopped())
270 61x return s;
271
272 now = Clock::now();
273 }
274 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 6x std::size_t poll()
289 {
290 6x 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 4x std::size_t poll_one()
305 {
306 4x 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 423x 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 1328x io_context& context() const noexcept
346 {
347 1328x 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 1342x bool running_in_this_thread() const noexcept
355 {
356 1342x 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 1347x void on_work_started() const noexcept
364 {
365 1347x ctx_->sched_->work_started();
366 1347x }
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 1321x void on_work_finished() const noexcept
374 {
375 1321x ctx_->sched_->work_finished();
376 1321x }
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 1340x std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
389 {
390 1340x if (running_in_this_thread())
391 825x return h;
392 515x ctx_->sched_->post(h);
393 515x 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 9043x void post(std::coroutine_handle<> h) const
404 {
405 9043x ctx_->sched_->post(h);
406 9043x }
407
408 /** Compare two executors for equality.
409
410 @return `true` if both executors refer to the same context.
411 */
412 1x bool operator==(executor_type const& other) const noexcept
413 {
414 1x 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 423x io_context::get_executor() const noexcept
429 {
430 423x return executor_type(const_cast<io_context&>(*this));
431 }
432
433 } // namespace boost::corosio
434
435 #endif // BOOST_COROSIO_IO_CONTEXT_HPP
436