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