libs/corosio/include/boost/corosio/basic_io_context.hpp

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