TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : // Copyright (c) 2026 Michael Vandeberg
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/corosio
10 : //
11 :
12 : #ifndef BOOST_COROSIO_IO_CONTEXT_HPP
13 : #define BOOST_COROSIO_IO_CONTEXT_HPP
14 :
15 : #include <boost/corosio/detail/config.hpp>
16 : #include <boost/corosio/detail/continuation_op.hpp>
17 : #include <boost/corosio/detail/platform.hpp>
18 : #include <boost/corosio/detail/scheduler.hpp>
19 : #include <boost/capy/continuation.hpp>
20 : #include <boost/capy/ex/execution_context.hpp>
21 :
22 : #include <chrono>
23 : #include <coroutine>
24 : #include <cstddef>
25 : #include <limits>
26 : #include <thread>
27 :
28 : namespace boost::corosio {
29 :
30 : namespace detail {
31 : struct timer_service_access;
32 : } // namespace detail
33 :
34 : /** An I/O context for running asynchronous operations.
35 :
36 : The io_context provides an execution environment for async
37 : operations. It maintains a queue of pending work items and
38 : processes them when `run()` is called.
39 :
40 : The default and unsigned constructors select the platform's
41 : native backend:
42 : - Windows: IOCP
43 : - Linux: epoll
44 : - BSD/macOS: kqueue
45 : - Other POSIX: select
46 :
47 : The template constructor accepts a backend tag value to
48 : choose a specific backend at compile time:
49 :
50 : @par Example
51 : @code
52 : io_context ioc; // platform default
53 : io_context ioc2(corosio::epoll); // explicit backend
54 : @endcode
55 :
56 : @par Thread Safety
57 : Distinct objects: Safe.@n
58 : Shared objects: Safe, if using a concurrency hint greater
59 : than 1.
60 :
61 : @see epoll_t, select_t, kqueue_t, iocp_t
62 : */
63 : class BOOST_COROSIO_DECL io_context : public capy::execution_context
64 : {
65 : friend struct detail::timer_service_access;
66 :
67 : protected:
68 : detail::scheduler* sched_;
69 :
70 : public:
71 : /** The executor type for this context. */
72 : class executor_type;
73 :
74 : /** Construct with default concurrency and platform backend. */
75 : io_context();
76 :
77 : /** Construct with a concurrency hint and platform backend.
78 :
79 : @param concurrency_hint Hint for the number of threads
80 : that will call `run()`.
81 : */
82 : explicit io_context(unsigned concurrency_hint);
83 :
84 : /** Construct with an explicit backend tag.
85 :
86 : @param backend The backend tag value selecting the I/O
87 : multiplexer (e.g. `corosio::epoll`).
88 : @param concurrency_hint Hint for the number of threads
89 : that will call `run()`.
90 : */
91 : template<class Backend>
92 : requires requires { Backend::construct; }
93 HIT 390 : explicit io_context(
94 : Backend backend,
95 : unsigned concurrency_hint = std::thread::hardware_concurrency())
96 : : capy::execution_context(this)
97 390 : , sched_(nullptr)
98 : {
99 : (void)backend;
100 390 : sched_ = &Backend::construct(*this, concurrency_hint);
101 390 : }
102 :
103 : ~io_context();
104 :
105 : io_context(io_context const&) = delete;
106 : io_context& operator=(io_context const&) = delete;
107 :
108 : /** Return an executor for this context.
109 :
110 : The returned executor can be used to dispatch coroutines
111 : and post work items to this context.
112 :
113 : @return An executor associated with this context.
114 : */
115 : executor_type get_executor() const noexcept;
116 :
117 : /** Signal the context to stop processing.
118 :
119 : This causes `run()` to return as soon as possible. Any pending
120 : work items remain queued.
121 : */
122 5 : void stop()
123 : {
124 5 : sched_->stop();
125 5 : }
126 :
127 : /** Return whether the context has been stopped.
128 :
129 : @return `true` if `stop()` has been called and `restart()`
130 : has not been called since.
131 : */
132 21 : bool stopped() const noexcept
133 : {
134 21 : return sched_->stopped();
135 : }
136 :
137 : /** Restart the context after being stopped.
138 :
139 : This function must be called before `run()` can be called
140 : again after `stop()` has been called.
141 : */
142 91 : void restart()
143 : {
144 91 : sched_->restart();
145 91 : }
146 :
147 : /** Process all pending work items.
148 :
149 : This function blocks until all pending work items have been
150 : executed or `stop()` is called. The context is stopped
151 : when there is no more outstanding work.
152 :
153 : @note The context must be restarted with `restart()` before
154 : calling this function again after it returns.
155 :
156 : @return The number of handlers executed.
157 : */
158 388 : std::size_t run()
159 : {
160 388 : return sched_->run();
161 : }
162 :
163 : /** Process at most one pending work item.
164 :
165 : This function blocks until one work item has been executed
166 : or `stop()` is called. The context is stopped when there
167 : is no more outstanding work.
168 :
169 : @note The context must be restarted with `restart()` before
170 : calling this function again after it returns.
171 :
172 : @return The number of handlers executed (0 or 1).
173 : */
174 2 : std::size_t run_one()
175 : {
176 2 : return sched_->run_one();
177 : }
178 :
179 : /** Process work items for the specified duration.
180 :
181 : This function blocks until work items have been executed for
182 : the specified duration, or `stop()` is called. The context
183 : 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 rel_time The duration for which to process work.
189 :
190 : @return The number of handlers executed.
191 : */
192 : template<class Rep, class Period>
193 8 : std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
194 : {
195 8 : return run_until(std::chrono::steady_clock::now() + rel_time);
196 : }
197 :
198 : /** Process work items until the specified time.
199 :
200 : This function blocks until the specified time is reached
201 : or `stop()` is called. The context is stopped when there
202 : is no more outstanding work.
203 :
204 : @note The context must be restarted with `restart()` before
205 : calling this function again after it returns.
206 :
207 : @param abs_time The time point until which to process work.
208 :
209 : @return The number of handlers executed.
210 : */
211 : template<class Clock, class Duration>
212 : std::size_t
213 8 : run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
214 : {
215 8 : std::size_t n = 0;
216 57 : while (run_one_until(abs_time))
217 49 : if (n != (std::numeric_limits<std::size_t>::max)())
218 49 : ++n;
219 8 : return n;
220 : }
221 :
222 : /** Process at most one work item for the specified duration.
223 :
224 : This function blocks until one work item has been executed,
225 : the specified duration has elapsed, or `stop()` is called.
226 : The context is stopped when there is no more outstanding work.
227 :
228 : @note The context must be restarted with `restart()` before
229 : calling this function again after it returns.
230 :
231 : @param rel_time The duration for which the call may block.
232 :
233 : @return The number of handlers executed (0 or 1).
234 : */
235 : template<class Rep, class Period>
236 2 : std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
237 : {
238 2 : return run_one_until(std::chrono::steady_clock::now() + rel_time);
239 : }
240 :
241 : /** Process at most one work item until the specified time.
242 :
243 : This function blocks until one work item has been executed,
244 : the specified time is reached, or `stop()` is called.
245 : The context is stopped when there is no more outstanding work.
246 :
247 : @note The context must be restarted with `restart()` before
248 : calling this function again after it returns.
249 :
250 : @param abs_time The time point until which the call may block.
251 :
252 : @return The number of handlers executed (0 or 1).
253 : */
254 : template<class Clock, class Duration>
255 : std::size_t
256 61 : run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
257 : {
258 61 : typename Clock::time_point now = Clock::now();
259 61 : while (now < abs_time)
260 : {
261 61 : auto rel_time = abs_time - now;
262 61 : if (rel_time > std::chrono::seconds(1))
263 MIS 0 : rel_time = std::chrono::seconds(1);
264 :
265 HIT 61 : std::size_t s = sched_->wait_one(
266 : static_cast<long>(
267 61 : std::chrono::duration_cast<std::chrono::microseconds>(
268 : rel_time)
269 61 : .count()));
270 :
271 61 : if (s || stopped())
272 61 : return s;
273 :
274 MIS 0 : now = Clock::now();
275 : }
276 0 : return 0;
277 : }
278 :
279 : /** Process all ready work items without blocking.
280 :
281 : This function executes all work items that are ready to run
282 : without blocking for more work. The context is stopped
283 : when there is no more outstanding work.
284 :
285 : @note The context must be restarted with `restart()` before
286 : calling this function again after it returns.
287 :
288 : @return The number of handlers executed.
289 : */
290 HIT 6 : std::size_t poll()
291 : {
292 6 : return sched_->poll();
293 : }
294 :
295 : /** Process at most one ready work item without blocking.
296 :
297 : This function executes at most one work item that is ready
298 : to run without blocking for more work. The context is
299 : stopped when there is no more outstanding work.
300 :
301 : @note The context must be restarted with `restart()` before
302 : calling this function again after it returns.
303 :
304 : @return The number of handlers executed (0 or 1).
305 : */
306 4 : std::size_t poll_one()
307 : {
308 4 : return sched_->poll_one();
309 : }
310 : };
311 :
312 : /** An executor for dispatching work to an I/O context.
313 :
314 : The executor provides the interface for posting work items and
315 : dispatching coroutines to the associated context. It satisfies
316 : the `capy::Executor` concept.
317 :
318 : Executors are lightweight handles that can be copied and compared
319 : for equality. Two executors compare equal if they refer to the
320 : same context.
321 :
322 : @par Thread Safety
323 : Distinct objects: Safe.@n
324 : Shared objects: Safe.
325 : */
326 : class io_context::executor_type
327 : {
328 : io_context* ctx_ = nullptr;
329 :
330 : public:
331 : /** Default constructor.
332 :
333 : Constructs an executor not associated with any context.
334 : */
335 : executor_type() = default;
336 :
337 : /** Construct an executor from a context.
338 :
339 : @param ctx The context to associate with this executor.
340 : */
341 606 : explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
342 :
343 : /** Return a reference to the associated execution context.
344 :
345 : @return Reference to the context.
346 : */
347 1278 : io_context& context() const noexcept
348 : {
349 1278 : return *ctx_;
350 : }
351 :
352 : /** Check if the current thread is running this executor's context.
353 :
354 : @return `true` if `run()` is being called on this thread.
355 : */
356 1290 : bool running_in_this_thread() const noexcept
357 : {
358 1290 : return ctx_->sched_->running_in_this_thread();
359 : }
360 :
361 : /** Informs the executor that work is beginning.
362 :
363 : Must be paired with `on_work_finished()`.
364 : */
365 1433 : void on_work_started() const noexcept
366 : {
367 1433 : ctx_->sched_->work_started();
368 1433 : }
369 :
370 : /** Informs the executor that work has completed.
371 :
372 : @par Preconditions
373 : A preceding call to `on_work_started()` on an equal executor.
374 : */
375 1407 : void on_work_finished() const noexcept
376 : {
377 1407 : ctx_->sched_->work_finished();
378 1407 : }
379 :
380 : /** Dispatch a continuation.
381 :
382 : Returns a handle for symmetric transfer. If called from
383 : within `run()`, returns `c.h`. Otherwise posts the
384 : enclosing continuation_op as a scheduler_op for later
385 : execution and returns `std::noop_coroutine()`.
386 :
387 : @param c The continuation to dispatch. Must be the `cont`
388 : member of a `detail::continuation_op`.
389 :
390 : @return A handle for symmetric transfer or `std::noop_coroutine()`.
391 : */
392 1288 : std::coroutine_handle<> dispatch(capy::continuation& c) const
393 : {
394 1288 : if (running_in_this_thread())
395 600 : return c.h;
396 688 : post(c);
397 688 : return std::noop_coroutine();
398 : }
399 :
400 : /** Post a continuation for deferred execution.
401 :
402 : If the continuation is backed by a continuation_op
403 : (tagged), posts it directly as a scheduler_op — zero
404 : heap allocation. Otherwise falls back to the
405 : heap-allocating post(coroutine_handle<>) path.
406 : */
407 9323 : void post(capy::continuation& c) const
408 : {
409 9323 : auto* op = detail::continuation_op::try_from_continuation(c);
410 9323 : if (op)
411 8632 : ctx_->sched_->post(op);
412 : else
413 691 : ctx_->sched_->post(c.h);
414 9323 : }
415 :
416 : /** Post a bare coroutine handle for deferred execution.
417 :
418 : Heap-allocates a scheduler_op to wrap the handle. Prefer
419 : posting through a continuation_op-backed continuation when
420 : the continuation has suitable lifetime.
421 :
422 : @param h The coroutine handle to post.
423 : */
424 1426 : void post(std::coroutine_handle<> h) const
425 : {
426 1426 : ctx_->sched_->post(h);
427 1426 : }
428 :
429 : /** Compare two executors for equality.
430 :
431 : @return `true` if both executors refer to the same context.
432 : */
433 1 : bool operator==(executor_type const& other) const noexcept
434 : {
435 1 : return ctx_ == other.ctx_;
436 : }
437 :
438 : /** Compare two executors for inequality.
439 :
440 : @return `true` if the executors refer to different contexts.
441 : */
442 : bool operator!=(executor_type const& other) const noexcept
443 : {
444 : return ctx_ != other.ctx_;
445 : }
446 : };
447 :
448 : inline io_context::executor_type
449 606 : io_context::get_executor() const noexcept
450 : {
451 606 : return executor_type(const_cast<io_context&>(*this));
452 : }
453 :
454 : } // namespace boost::corosio
455 :
456 : #endif // BOOST_COROSIO_IO_CONTEXT_HPP
|