TLA 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 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 HIT 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 MIS 0 : rel_time = std::chrono::seconds(1);
202 :
203 HIT 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 MIS 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 HIT 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
|