1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
// Copyright (c) 2026 Steve Gerbino
3  
// Copyright (c) 2026 Steve Gerbino
4  
// Copyright (c) 2026 Michael Vandeberg
4  
// Copyright (c) 2026 Michael Vandeberg
5  
//
5  
//
6  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
7  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8  
//
8  
//
9  
// Official repository: https://github.com/cppalliance/corosio
9  
// Official repository: https://github.com/cppalliance/corosio
10  
//
10  
//
11  

11  

12  
#ifndef BOOST_COROSIO_IO_CONTEXT_HPP
12  
#ifndef BOOST_COROSIO_IO_CONTEXT_HPP
13  
#define BOOST_COROSIO_IO_CONTEXT_HPP
13  
#define BOOST_COROSIO_IO_CONTEXT_HPP
14  

14  

15  
#include <boost/corosio/detail/config.hpp>
15  
#include <boost/corosio/detail/config.hpp>
16  
#include <boost/corosio/detail/platform.hpp>
16  
#include <boost/corosio/detail/platform.hpp>
17 -
#include <boost/corosio/basic_io_context.hpp>
17 +
#include <boost/corosio/detail/scheduler.hpp>
18 -

18 +
#include <boost/capy/ex/execution_context.hpp>
19 -
// Include the platform-specific context headers
 
20 -
#if BOOST_COROSIO_HAS_IOCP
 
21 -
#include <boost/corosio/iocp_context.hpp>
 
22 -
#endif
 
23 -

 
24 -
#if BOOST_COROSIO_HAS_EPOLL
 
25 -
#include <boost/corosio/epoll_context.hpp>
 
26 -
#endif
 
27 -

 
28 -
#if BOOST_COROSIO_HAS_KQUEUE
 
29 -
#include <boost/corosio/kqueue_context.hpp>
 
30 -
#endif
 
31  

19  

32 -
#if BOOST_COROSIO_HAS_SELECT
20 +
#include <chrono>
33 -
#include <boost/corosio/select_context.hpp>
21 +
#include <coroutine>
34 -
#endif
22 +
#include <cstddef>
 
23 +
#include <limits>
 
24 +
#include <thread>
35  

25  

36  
namespace boost::corosio {
26  
namespace boost::corosio {
37  

27  

 
28 +
namespace detail {
 
29 +
struct timer_service_access;
 
30 +
} // namespace detail
 
31 +

38  
/** An I/O context for running asynchronous operations.
32  
/** An I/O context for running asynchronous operations.
39  

33  

40 -
    The io_context provides an execution environment for async operations.
34 +
    The io_context provides an execution environment for async
41 -
    It maintains a queue of pending work items and processes them when
35 +
    operations. It maintains a queue of pending work items and
42 -
    `run()` is called.
36 +
    processes them when `run()` is called.
43  

37  

44 -
    This is a type alias for the platform's default I/O backend:
38 +
    The default and unsigned constructors select the platform's
45 -
    - Windows: `iocp_context` (I/O Completion Ports)
39 +
    native backend:
46 -
    - Linux: `epoll_context` (epoll)
40 +
    - Windows: IOCP
47 -
    - BSD/macOS: `kqueue_context` (kqueue) (macOS verified, BSD future)
41 +
    - Linux: epoll
48 -
    - Other POSIX: `select_context` (select)
42 +
    - BSD/macOS: kqueue
 
43 +
    - Other POSIX: select
49  

44  

50 -
    For explicit backend selection, use the concrete context types
45 +
    The template constructor accepts a backend tag value to
51 -
    directly (e.g., `epoll_context`, `iocp_context`).
46 +
    choose a specific backend at compile time:
52  

47  

53 -
    The nested `executor_type` class provides the interface for dispatching
48 +
    @par Example
54 -
    coroutines and posting work items. It implements both synchronous
49 +
    @code
55 -
    dispatch (for symmetric transfer) and deferred posting.
50 +
    io_context ioc;                   // platform default
 
51 +
    io_context ioc2(corosio::epoll);  // explicit backend
 
52 +
    @endcode
56  

53  

57  
    @par Thread Safety
54  
    @par Thread Safety
58  
    Distinct objects: Safe.@n
55  
    Distinct objects: Safe.@n
59 -
    Shared objects: Safe, if using a concurrency hint greater than 1.
56 +
    Shared objects: Safe, if using a concurrency hint greater
 
57 +
    than 1.
60  

58  

61 -
    @par Example
59 +
    @see epoll_t, select_t, kqueue_t, iocp_t
62 -
    @code
60 +
*/
63 -
    io_context ioc;
61 +
class BOOST_COROSIO_DECL io_context : public capy::execution_context
64 -
    auto ex = ioc.get_executor();
62 +
{
65 -
    run_async(ex)(my_coroutine());
63 +
    friend struct detail::timer_service_access;
66 -
    ioc.run();  // Process all queued work
 
67 -
    @endcode
 
68  

64  

69 -
    @par Explicit Backend Selection
65 +
protected:
70 -
    @code
66 +
    detail::scheduler* sched_;
71 -
    // Use epoll explicitly (Linux)
 
72 -
    epoll_context ctx;
 
73  

67  

74 -
    // Generic code using IoContext concept
68 +
public:
75 -
    template<IoContext Ctx>
69 +
    /** The executor type for this context. */
76 -
    void run_server(Ctx& ctx) {
70 +
    class executor_type;
77 -
        ctx.run();
71 +

 
72 +
    /** Construct with default concurrency and platform backend. */
 
73 +
    io_context();
 
74 +

 
75 +
    /** Construct with a concurrency hint and platform backend.
 
76 +

 
77 +
        @param concurrency_hint Hint for the number of threads
 
78 +
            that will call `run()`.
 
79 +
    */
 
80 +
    explicit io_context(unsigned concurrency_hint);
 
81 +

 
82 +
    /** Construct with an explicit backend tag.
 
83 +

 
84 +
        @param backend The backend tag value selecting the I/O
 
85 +
            multiplexer (e.g. `corosio::epoll`).
 
86 +
        @param concurrency_hint Hint for the number of threads
 
87 +
            that will call `run()`.
 
88 +
    */
 
89 +
    template<class Backend>
 
90 +
        requires requires { Backend::construct; }
 
91 +
    explicit io_context(
 
92 +
        Backend backend,
 
93 +
        unsigned concurrency_hint = std::thread::hardware_concurrency())
 
94 +
        : capy::execution_context(this)
 
95 +
        , sched_(nullptr)
 
96 +
    {
 
97 +
        (void)backend;
 
98 +
        sched_ = &Backend::construct(*this, concurrency_hint);
78  
    }
99  
    }
79 -
    @endcode
100 +

 
101 +
    ~io_context();
 
102 +

 
103 +
    io_context(io_context const&)            = delete;
 
104 +
    io_context& operator=(io_context const&) = delete;
 
105 +

 
106 +
    /** Return an executor for this context.
 
107 +

 
108 +
        The returned executor can be used to dispatch coroutines
 
109 +
        and post work items to this context.
 
110 +

 
111 +
        @return An executor associated with this context.
 
112 +
    */
 
113 +
    executor_type get_executor() const noexcept;
 
114 +

 
115 +
    /** Signal the context to stop processing.
 
116 +

 
117 +
        This causes `run()` to return as soon as possible. Any pending
 
118 +
        work items remain queued.
 
119 +
    */
 
120 +
    void stop()
 
121 +
    {
 
122 +
        sched_->stop();
 
123 +
    }
 
124 +

 
125 +
    /** Return whether the context has been stopped.
 
126 +

 
127 +
        @return `true` if `stop()` has been called and `restart()`
 
128 +
            has not been called since.
 
129 +
    */
 
130 +
    bool stopped() const noexcept
 
131 +
    {
 
132 +
        return sched_->stopped();
 
133 +
    }
 
134 +

 
135 +
    /** Restart the context after being stopped.
 
136 +

 
137 +
        This function must be called before `run()` can be called
 
138 +
        again after `stop()` has been called.
 
139 +
    */
 
140 +
    void restart()
 
141 +
    {
 
142 +
        sched_->restart();
 
143 +
    }
 
144 +

 
145 +
    /** Process all pending work items.
 
146 +

 
147 +
        This function blocks until all pending work items have been
 
148 +
        executed or `stop()` is called. The context is stopped
 
149 +
        when there is no more outstanding work.
 
150 +

 
151 +
        @note The context must be restarted with `restart()` before
 
152 +
            calling this function again after it returns.
 
153 +

 
154 +
        @return The number of handlers executed.
 
155 +
    */
 
156 +
    std::size_t run()
 
157 +
    {
 
158 +
        return sched_->run();
 
159 +
    }
 
160 +

 
161 +
    /** Process at most one pending work item.
 
162 +

 
163 +
        This function blocks until one work item has been executed
 
164 +
        or `stop()` is called. The context is stopped when there
 
165 +
        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 +
        @return The number of handlers executed (0 or 1).
 
171 +
    */
 
172 +
    std::size_t run_one()
 
173 +
    {
 
174 +
        return sched_->run_one();
 
175 +
    }
 
176 +

 
177 +
    /** Process work items for the specified duration.
 
178 +

 
179 +
        This function blocks until work items have been executed for
 
180 +
        the specified duration, or `stop()` is called. The context
 
181 +
        is stopped when there is no more outstanding work.
 
182 +

 
183 +
        @note The context must be restarted with `restart()` before
 
184 +
            calling this function again after it returns.
 
185 +

 
186 +
        @param rel_time The duration for which to process work.
 
187 +

 
188 +
        @return The number of handlers executed.
 
189 +
    */
 
190 +
    template<class Rep, class Period>
 
191 +
    std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
 
192 +
    {
 
193 +
        return run_until(std::chrono::steady_clock::now() + rel_time);
 
194 +
    }
 
195 +

 
196 +
    /** Process work items until the specified time.
 
197 +

 
198 +
        This function blocks until the specified time is reached
 
199 +
        or `stop()` is called. The context is stopped when there
 
200 +
        is no more outstanding work.
 
201 +

 
202 +
        @note The context must be restarted with `restart()` before
 
203 +
            calling this function again after it returns.
 
204 +

 
205 +
        @param abs_time The time point until which to process work.
 
206 +

 
207 +
        @return The number of handlers executed.
 
208 +
    */
 
209 +
    template<class Clock, class Duration>
 
210 +
    std::size_t
 
211 +
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
 
212 +
    {
 
213 +
        std::size_t n = 0;
 
214 +
        while (run_one_until(abs_time))
 
215 +
            if (n != (std::numeric_limits<std::size_t>::max)())
 
216 +
                ++n;
 
217 +
        return n;
 
218 +
    }
 
219 +

 
220 +
    /** Process at most one work item for the specified duration.
 
221 +

 
222 +
        This function blocks until one work item has been executed,
 
223 +
        the specified duration has elapsed, or `stop()` is called.
 
224 +
        The context is stopped when there is no more outstanding work.
 
225 +

 
226 +
        @note The context must be restarted with `restart()` before
 
227 +
            calling this function again after it returns.
 
228 +

 
229 +
        @param rel_time The duration for which the call may block.
 
230 +

 
231 +
        @return The number of handlers executed (0 or 1).
 
232 +
    */
 
233 +
    template<class Rep, class Period>
 
234 +
    std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
 
235 +
    {
 
236 +
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
 
237 +
    }
 
238 +

 
239 +
    /** Process at most one work item until the specified time.
 
240 +

 
241 +
        This function blocks until one work item has been executed,
 
242 +
        the specified time is reached, or `stop()` is called.
 
243 +
        The context is stopped when there is no more outstanding work.
 
244 +

 
245 +
        @note The context must be restarted with `restart()` before
 
246 +
            calling this function again after it returns.
 
247 +

 
248 +
        @param abs_time The time point until which the call may block.
 
249 +

 
250 +
        @return The number of handlers executed (0 or 1).
 
251 +
    */
 
252 +
    template<class Clock, class Duration>
 
253 +
    std::size_t
 
254 +
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
 
255 +
    {
 
256 +
        typename Clock::time_point now = Clock::now();
 
257 +
        while (now < abs_time)
 
258 +
        {
 
259 +
            auto rel_time = abs_time - now;
 
260 +
            if (rel_time > std::chrono::seconds(1))
 
261 +
                rel_time = std::chrono::seconds(1);
 
262 +

 
263 +
            std::size_t s = sched_->wait_one(
 
264 +
                static_cast<long>(
 
265 +
                    std::chrono::duration_cast<std::chrono::microseconds>(
 
266 +
                        rel_time)
 
267 +
                        .count()));
 
268 +

 
269 +
            if (s || stopped())
 
270 +
                return s;
 
271 +

 
272 +
            now = Clock::now();
 
273 +
        }
 
274 +
        return 0;
 
275 +
    }
 
276 +

 
277 +
    /** Process all ready work items without blocking.
 
278 +

 
279 +
        This function executes all work items that are ready to run
 
280 +
        without blocking for more work. The context is stopped
 
281 +
        when there is no more outstanding work.
 
282 +

 
283 +
        @note The context must be restarted with `restart()` before
 
284 +
            calling this function again after it returns.
 
285 +

 
286 +
        @return The number of handlers executed.
 
287 +
    */
 
288 +
    std::size_t poll()
 
289 +
    {
 
290 +
        return sched_->poll();
 
291 +
    }
 
292 +

 
293 +
    /** Process at most one ready work item without blocking.
 
294 +

 
295 +
        This function executes at most one work item that is ready
 
296 +
        to run without blocking for more work. The context is
 
297 +
        stopped when there is no more outstanding work.
 
298 +

 
299 +
        @note The context must be restarted with `restart()` before
 
300 +
            calling this function again after it returns.
 
301 +

 
302 +
        @return The number of handlers executed (0 or 1).
 
303 +
    */
 
304 +
    std::size_t poll_one()
 
305 +
    {
 
306 +
        return sched_->poll_one();
 
307 +
    }
 
308 +
};
 
309 +

 
310 +
/** An executor for dispatching work to an I/O context.
 
311 +

 
312 +
    The executor provides the interface for posting work items and
 
313 +
    dispatching coroutines to the associated context. It satisfies
 
314 +
    the `capy::Executor` concept.
 
315 +

 
316 +
    Executors are lightweight handles that can be copied and compared
 
317 +
    for equality. Two executors compare equal if they refer to the
 
318 +
    same context.
 
319 +

 
320 +
    @par Thread Safety
 
321 +
    Distinct objects: Safe.@n
 
322 +
    Shared objects: Safe.
80  
*/
323  
*/
81 -
#if BOOST_COROSIO_HAS_IOCP
324 +
class io_context::executor_type
82 -
using io_context = iocp_context;
325 +
{
83 -
#elif BOOST_COROSIO_HAS_EPOLL
326 +
    io_context* ctx_ = nullptr;
84 -
using io_context = epoll_context;
327 +

85 -
#elif BOOST_COROSIO_HAS_KQUEUE
328 +
public:
86 -
using io_context = kqueue_context;
329 +
    /** Default constructor.
87 -
#elif BOOST_COROSIO_HAS_SELECT
330 +

88 -
using io_context = select_context;
331 +
        Constructs an executor not associated with any context.
89 -
#endif
332 +
    */
 
333 +
    executor_type() = default;
 
334 +

 
335 +
    /** Construct an executor from a context.
 
336 +

 
337 +
        @param ctx The context to associate with this executor.
 
338 +
    */
 
339 +
    explicit executor_type(io_context& ctx) noexcept : ctx_(&ctx) {}
 
340 +

 
341 +
    /** Return a reference to the associated execution context.
 
342 +

 
343 +
        @return Reference to the context.
 
344 +
    */
 
345 +
    io_context& context() const noexcept
 
346 +
    {
 
347 +
        return *ctx_;
 
348 +
    }
 
349 +

 
350 +
    /** Check if the current thread is running this executor's context.
 
351 +

 
352 +
        @return `true` if `run()` is being called on this thread.
 
353 +
    */
 
354 +
    bool running_in_this_thread() const noexcept
 
355 +
    {
 
356 +
        return ctx_->sched_->running_in_this_thread();
 
357 +
    }
 
358 +

 
359 +
    /** Informs the executor that work is beginning.
 
360 +

 
361 +
        Must be paired with `on_work_finished()`.
 
362 +
    */
 
363 +
    void on_work_started() const noexcept
 
364 +
    {
 
365 +
        ctx_->sched_->work_started();
 
366 +
    }
 
367 +

 
368 +
    /** Informs the executor that work has completed.
 
369 +

 
370 +
        @par Preconditions
 
371 +
        A preceding call to `on_work_started()` on an equal executor.
 
372 +
    */
 
373 +
    void on_work_finished() const noexcept
 
374 +
    {
 
375 +
        ctx_->sched_->work_finished();
 
376 +
    }
 
377 +

 
378 +
    /** Dispatch a coroutine handle.
 
379 +

 
380 +
        Returns a handle for symmetric transfer. If called from
 
381 +
        within `run()`, returns `h`. Otherwise posts the coroutine
 
382 +
        for later execution and returns `std::noop_coroutine()`.
 
383 +

 
384 +
        @param h The coroutine handle to dispatch.
 
385 +

 
386 +
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
 
387 +
    */
 
388 +
    std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
 
389 +
    {
 
390 +
        if (running_in_this_thread())
 
391 +
            return h;
 
392 +
        ctx_->sched_->post(h);
 
393 +
        return std::noop_coroutine();
 
394 +
    }
 
395 +

 
396 +
    /** Post a coroutine for deferred execution.
 
397 +

 
398 +
        The coroutine will be resumed during a subsequent call to
 
399 +
        `run()`.
 
400 +

 
401 +
        @param h The coroutine handle to post.
 
402 +
    */
 
403 +
    void post(std::coroutine_handle<> h) const
 
404 +
    {
 
405 +
        ctx_->sched_->post(h);
 
406 +
    }
 
407 +

 
408 +
    /** Compare two executors for equality.
 
409 +

 
410 +
        @return `true` if both executors refer to the same context.
 
411 +
    */
 
412 +
    bool operator==(executor_type const& other) const noexcept
 
413 +
    {
 
414 +
        return ctx_ == other.ctx_;
 
415 +
    }
 
416 +

 
417 +
    /** Compare two executors for inequality.
 
418 +

 
419 +
        @return `true` if the executors refer to different contexts.
 
420 +
    */
 
421 +
    bool operator!=(executor_type const& other) const noexcept
 
422 +
    {
 
423 +
        return ctx_ != other.ctx_;
 
424 +
    }
 
425 +
};
 
426 +

 
427 +
inline io_context::executor_type
 
428 +
io_context::get_executor() const noexcept
 
429 +
{
 
430 +
    return executor_type(const_cast<io_context&>(*this));
 
431 +
}
90  

432  

91  
} // namespace boost::corosio
433  
} // namespace boost::corosio
92  

434  

93  
#endif // BOOST_COROSIO_IO_CONTEXT_HPP
435  
#endif // BOOST_COROSIO_IO_CONTEXT_HPP