include/boost/corosio/io_context.hpp

95.5% Lines (63/66) 100.0% Functions (22/22)
include/boost/corosio/io_context.hpp
Line 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 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 rel_time = std::chrono::seconds(1);
262
263 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 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 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 1240 io_context& context() const noexcept
346 {
347 1240 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 1260 bool running_in_this_thread() const noexcept
355 {
356 1260 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 1265 void on_work_started() const noexcept
364 {
365 1265 ctx_->sched_->work_started();
366 1265 }
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 1239 void on_work_finished() const noexcept
374 {
375 1239 ctx_->sched_->work_finished();
376 1239 }
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 1258 std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
389 {
390 1258 if (running_in_this_thread())
391 823 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 10048 void post(std::coroutine_handle<> h) const
404 {
405 10048 ctx_->sched_->post(h);
406 10048 }
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
436