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/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 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 57 : while (run_one_until(abs_time))
162 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 : 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 61 : while (now < abs_time)
206 : {
207 61 : auto rel_time = abs_time - now;
208 61 : if (rel_time > std::chrono::seconds(1))
209 0 : rel_time = std::chrono::seconds(1);
210 :
211 61 : std::size_t s = sched_->wait_one(
212 : static_cast<long>(std::chrono::duration_cast<
213 61 : std::chrono::microseconds>(rel_time).count()));
214 :
215 61 : if (s || stopped())
216 61 : return s;
217 :
218 0 : now = Clock::now();
219 : }
220 0 : 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 1090 : context() const noexcept
312 : {
313 1090 : 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 150780 : running_in_this_thread() const noexcept
322 : {
323 150780 : 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 1115 : on_work_started() const noexcept
332 : {
333 1115 : ctx_->sched_->on_work_started();
334 1115 : }
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 1089 : on_work_finished() const noexcept
343 : {
344 1089 : ctx_->sched_->on_work_finished();
345 1089 : }
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 150778 : dispatch(std::coroutine_handle<> h) const
359 : {
360 150778 : if (running_in_this_thread())
361 150355 : 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 10633 : post(std::coroutine_handle<> h) const
375 : {
376 10633 : ctx_->sched_->post(h);
377 10633 : }
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
|