include/boost/corosio/basic_io_context.hpp

95.2% Lines (60/63) 100.0% Functions (21/21)
include/boost/corosio/basic_io_context.hpp
Line Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
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/corosio
8 //
9
10 #ifndef BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
11 #define BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/detail/scheduler.hpp>
15 #include <boost/capy/ex/execution_context.hpp>
16
17 #include <chrono>
18 #include <coroutine>
19 #include <cstddef>
20 #include <limits>
21
22 namespace boost::corosio {
23
24 namespace detail {
25 struct timer_service_access;
26 } // namespace detail
27
28 /** Base class for I/O context implementations.
29
30 This class provides the common API for all I/O context types.
31 Concrete context implementations (epoll_context, iocp_context, etc.)
32 inherit from this class to gain the standard io_context interface.
33
34 @par Thread Safety
35 Distinct objects: Safe.@n
36 Shared objects: Safe, if using a concurrency hint greater than 1.
37 */
38 class BOOST_COROSIO_DECL basic_io_context : public capy::execution_context
39 {
40 friend struct detail::timer_service_access;
41
42 public:
43 /** The executor type for this context. */
44 class executor_type;
45
46 /** Return an executor for this context.
47
48 The returned executor can be used to dispatch coroutines
49 and post work items to this context.
50
51 @return An executor associated with this context.
52 */
53 executor_type get_executor() const noexcept;
54
55 /** Signal the context to stop processing.
56
57 This causes `run()` to return as soon as possible. Any pending
58 work items remain queued.
59 */
60 1 void stop()
61 {
62 1 sched_->stop();
63 1 }
64
65 /** Return whether the context has been stopped.
66
67 @return `true` if `stop()` has been called and `restart()`
68 has not been called since.
69 */
70 21 bool stopped() const noexcept
71 {
72 21 return sched_->stopped();
73 }
74
75 /** Restart the context after being stopped.
76
77 This function must be called before `run()` can be called
78 again after `stop()` has been called.
79 */
80 83 void restart()
81 {
82 83 sched_->restart();
83 83 }
84
85 /** Process all pending work items.
86
87 This function blocks until all pending work items have been
88 executed or `stop()` is called. The context is stopped
89 when there is no more outstanding work.
90
91 @note The context must be restarted with `restart()` before
92 calling this function again after it returns.
93
94 @return The number of handlers executed.
95 */
96 279 std::size_t run()
97 {
98 279 return sched_->run();
99 }
100
101 /** Process at most one pending work item.
102
103 This function blocks until one work item has been executed
104 or `stop()` is called. The context is stopped when there
105 is no more outstanding work.
106
107 @note The context must be restarted with `restart()` before
108 calling this function again after it returns.
109
110 @return The number of handlers executed (0 or 1).
111 */
112 2 std::size_t run_one()
113 {
114 2 return sched_->run_one();
115 }
116
117 /** Process work items for the specified duration.
118
119 This function blocks until work items have been executed for
120 the specified duration, or `stop()` is called. The context
121 is stopped when there is no more outstanding work.
122
123 @note The context must be restarted with `restart()` before
124 calling this function again after it returns.
125
126 @param rel_time The duration for which to process work.
127
128 @return The number of handlers executed.
129 */
130 template<class Rep, class Period>
131 8 std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
132 {
133 8 return run_until(std::chrono::steady_clock::now() + rel_time);
134 }
135
136 /** Process work items until the specified time.
137
138 This function blocks until the specified time is reached
139 or `stop()` is called. The context is stopped when there
140 is no more outstanding work.
141
142 @note The context must be restarted with `restart()` before
143 calling this function again after it returns.
144
145 @param abs_time The time point until which to process work.
146
147 @return The number of handlers executed.
148 */
149 template<class Clock, class Duration>
150 std::size_t
151 8 run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
152 {
153 8 std::size_t n = 0;
154 57 while (run_one_until(abs_time))
155 49 if (n != (std::numeric_limits<std::size_t>::max)())
156 49 ++n;
157 8 return n;
158 }
159
160 /** Process at most one work item for the specified duration.
161
162 This function blocks until one work item has been executed,
163 the specified duration has elapsed, or `stop()` is called.
164 The context is stopped when there is no more outstanding work.
165
166 @note The context must be restarted with `restart()` before
167 calling this function again after it returns.
168
169 @param rel_time The duration for which the call may block.
170
171 @return The number of handlers executed (0 or 1).
172 */
173 template<class Rep, class Period>
174 2 std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
175 {
176 2 return run_one_until(std::chrono::steady_clock::now() + rel_time);
177 }
178
179 /** Process at most one work item until the specified time.
180
181 This function blocks until one work item has been executed,
182 the specified time is reached, or `stop()` is called.
183 The context is stopped when there is no more outstanding work.
184
185 @note The context must be restarted with `restart()` before
186 calling this function again after it returns.
187
188 @param abs_time The time point until which the call may block.
189
190 @return The number of handlers executed (0 or 1).
191 */
192 template<class Clock, class Duration>
193 std::size_t
194 61 run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
195 {
196 61 typename Clock::time_point now = Clock::now();
197 61 while (now < abs_time)
198 {
199 61 auto rel_time = abs_time - now;
200 61 if (rel_time > std::chrono::seconds(1))
201 rel_time = std::chrono::seconds(1);
202
203 61 std::size_t s = sched_->wait_one(
204 static_cast<long>(
205 61 std::chrono::duration_cast<std::chrono::microseconds>(
206 rel_time)
207 61 .count()));
208
209 61 if (s || stopped())
210 61 return s;
211
212 now = Clock::now();
213 }
214 return 0;
215 }
216
217 /** Process all ready work items without blocking.
218
219 This function executes all work items that are ready to run
220 without blocking for more work. The context is stopped
221 when there is no more outstanding work.
222
223 @note The context must be restarted with `restart()` before
224 calling this function again after it returns.
225
226 @return The number of handlers executed.
227 */
228 2 std::size_t poll()
229 {
230 2 return sched_->poll();
231 }
232
233 /** Process at most one ready work item without blocking.
234
235 This function executes at most one work item that is ready
236 to run without blocking for more work. The context is
237 stopped when there is no more outstanding work.
238
239 @note The context must be restarted with `restart()` before
240 calling this function again after it returns.
241
242 @return The number of handlers executed (0 or 1).
243 */
244 4 std::size_t poll_one()
245 {
246 4 return sched_->poll_one();
247 }
248
249 protected:
250 /** Default constructor.
251
252 Derived classes must set sched_ in their constructor body.
253 */
254 336 basic_io_context() : capy::execution_context(this), sched_(nullptr) {}
255
256 detail::scheduler* sched_;
257 };
258
259 /** An executor for dispatching work to an I/O context.
260
261 The executor provides the interface for posting work items and
262 dispatching coroutines to the associated context. It satisfies
263 the `capy::Executor` concept.
264
265 Executors are lightweight handles that can be copied and compared
266 for equality. Two executors compare equal if they refer to the
267 same context.
268
269 @par Thread Safety
270 Distinct objects: Safe.@n
271 Shared objects: Safe.
272 */
273 class basic_io_context::executor_type
274 {
275 basic_io_context* ctx_ = nullptr;
276
277 public:
278 /** Default constructor.
279
280 Constructs an executor not associated with any context.
281 */
282 executor_type() = default;
283
284 /** Construct an executor from a context.
285
286 @param ctx The context to associate with this executor.
287 */
288 356 explicit executor_type(basic_io_context& ctx) noexcept : ctx_(&ctx) {}
289
290 /** Return a reference to the associated execution context.
291
292 @return Reference to the context.
293 */
294 1230 basic_io_context& context() const noexcept
295 {
296 1230 return *ctx_;
297 }
298
299 /** Check if the current thread is running this executor's context.
300
301 @return `true` if `run()` is being called on this thread.
302 */
303 1250 bool running_in_this_thread() const noexcept
304 {
305 1250 return ctx_->sched_->running_in_this_thread();
306 }
307
308 /** Informs the executor that work is beginning.
309
310 Must be paired with `on_work_finished()`.
311 */
312 1255 void on_work_started() const noexcept
313 {
314 1255 ctx_->sched_->work_started();
315 1255 }
316
317 /** Informs the executor that work has completed.
318
319 @par Preconditions
320 A preceding call to `on_work_started()` on an equal executor.
321 */
322 1229 void on_work_finished() const noexcept
323 {
324 1229 ctx_->sched_->work_finished();
325 1229 }
326
327 /** Dispatch a coroutine handle.
328
329 Returns a handle for symmetric transfer. If called from
330 within `run()`, returns `h`. Otherwise posts the coroutine
331 for later execution and returns `std::noop_coroutine()`.
332
333 @param h The coroutine handle to dispatch.
334
335 @return A handle for symmetric transfer or `std::noop_coroutine()`.
336 */
337 1248 std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
338 {
339 1248 if (running_in_this_thread())
340 825 return h;
341 423 ctx_->sched_->post(h);
342 423 return std::noop_coroutine();
343 }
344
345 /** Post a coroutine for deferred execution.
346
347 The coroutine will be resumed during a subsequent call to
348 `run()`.
349
350 @param h The coroutine handle to post.
351 */
352 10557 void post(std::coroutine_handle<> h) const
353 {
354 10557 ctx_->sched_->post(h);
355 10557 }
356
357 /** Compare two executors for equality.
358
359 @return `true` if both executors refer to the same context.
360 */
361 1 bool operator==(executor_type const& other) const noexcept
362 {
363 1 return ctx_ == other.ctx_;
364 }
365
366 /** Compare two executors for inequality.
367
368 @return `true` if the executors refer to different contexts.
369 */
370 bool operator!=(executor_type const& other) const noexcept
371 {
372 return ctx_ != other.ctx_;
373 }
374 };
375
376 inline basic_io_context::executor_type
377 356 basic_io_context::get_executor() const noexcept
378 {
379 356 return executor_type(const_cast<basic_io_context&>(*this));
380 }
381
382 } // namespace boost::corosio
383
384 #endif // BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
385