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

9  

10  
#ifndef BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
10  
#ifndef BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
11  
#define BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
11  
#define BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
12  

12  

13  
#include <boost/corosio/detail/config.hpp>
13  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/scheduler.hpp>
14  
#include <boost/corosio/detail/scheduler.hpp>
15  
#include <boost/capy/ex/execution_context.hpp>
15  
#include <boost/capy/ex/execution_context.hpp>
16  

16  

17  
#include <chrono>
17  
#include <chrono>
18  
#include <coroutine>
18  
#include <coroutine>
19  
#include <cstddef>
19  
#include <cstddef>
20  
#include <limits>
20  
#include <limits>
21  

21  

22  
namespace boost::corosio {
22  
namespace boost::corosio {
23  

23  

24  
namespace detail {
24  
namespace detail {
25  
struct timer_service_access;
25  
struct timer_service_access;
26  
} // namespace detail
26  
} // namespace detail
27  

27  

28  
/** Base class for I/O context implementations.
28  
/** Base class for I/O context implementations.
29  

29  

30  
    This class provides the common API for all I/O context types.
30  
    This class provides the common API for all I/O context types.
31  
    Concrete context implementations (epoll_context, iocp_context, etc.)
31  
    Concrete context implementations (epoll_context, iocp_context, etc.)
32  
    inherit from this class to gain the standard io_context interface.
32  
    inherit from this class to gain the standard io_context interface.
33  

33  

34  
    @par Thread Safety
34  
    @par Thread Safety
35  
    Distinct objects: Safe.@n
35  
    Distinct objects: Safe.@n
36  
    Shared objects: Safe, if using a concurrency hint greater than 1.
36  
    Shared objects: Safe, if using a concurrency hint greater than 1.
37  
*/
37  
*/
38  
class BOOST_COROSIO_DECL basic_io_context : public capy::execution_context
38  
class BOOST_COROSIO_DECL basic_io_context : public capy::execution_context
39  
{
39  
{
40  
    friend struct detail::timer_service_access;
40  
    friend struct detail::timer_service_access;
41  

41  

42  
public:
42  
public:
43  
    /** The executor type for this context. */
43  
    /** The executor type for this context. */
44  
    class executor_type;
44  
    class executor_type;
45  

45  

46  
    /** Return an executor for this context.
46  
    /** Return an executor for this context.
47  

47  

48  
        The returned executor can be used to dispatch coroutines
48  
        The returned executor can be used to dispatch coroutines
49  
        and post work items to this context.
49  
        and post work items to this context.
50  

50  

51  
        @return An executor associated with this context.
51  
        @return An executor associated with this context.
52  
    */
52  
    */
53 -
    executor_type
53 +
    executor_type get_executor() const noexcept;
54 -
    get_executor() const noexcept;
 
55  

54  

56  
    /** Signal the context to stop processing.
55  
    /** Signal the context to stop processing.
57  

56  

58  
        This causes `run()` to return as soon as possible. Any pending
57  
        This causes `run()` to return as soon as possible. Any pending
59  
        work items remain queued.
58  
        work items remain queued.
60  
    */
59  
    */
61 -
    void
60 +
    void stop()
62 -
    stop()
 
63  
    {
61  
    {
64  
        sched_->stop();
62  
        sched_->stop();
65  
    }
63  
    }
66  

64  

67  
    /** Return whether the context has been stopped.
65  
    /** Return whether the context has been stopped.
68  

66  

69  
        @return `true` if `stop()` has been called and `restart()`
67  
        @return `true` if `stop()` has been called and `restart()`
70  
            has not been called since.
68  
            has not been called since.
71  
    */
69  
    */
72 -
    bool
70 +
    bool stopped() const noexcept
73 -
    stopped() const noexcept
 
74  
    {
71  
    {
75  
        return sched_->stopped();
72  
        return sched_->stopped();
76  
    }
73  
    }
77  

74  

78  
    /** Restart the context after being stopped.
75  
    /** Restart the context after being stopped.
79  

76  

80  
        This function must be called before `run()` can be called
77  
        This function must be called before `run()` can be called
81  
        again after `stop()` has been called.
78  
        again after `stop()` has been called.
82  
    */
79  
    */
83 -
    void
80 +
    void restart()
84 -
    restart()
 
85  
    {
81  
    {
86  
        sched_->restart();
82  
        sched_->restart();
87  
    }
83  
    }
88  

84  

89  
    /** Process all pending work items.
85  
    /** Process all pending work items.
90  

86  

91  
        This function blocks until all pending work items have been
87  
        This function blocks until all pending work items have been
92  
        executed or `stop()` is called. The context is stopped
88  
        executed or `stop()` is called. The context is stopped
93  
        when there is no more outstanding work.
89  
        when there is no more outstanding work.
94  

90  

95  
        @note The context must be restarted with `restart()` before
91  
        @note The context must be restarted with `restart()` before
96  
            calling this function again after it returns.
92  
            calling this function again after it returns.
97  

93  

98  
        @return The number of handlers executed.
94  
        @return The number of handlers executed.
99  
    */
95  
    */
100 -
    std::size_t
96 +
    std::size_t run()
101 -
    run()
 
102  
    {
97  
    {
103  
        return sched_->run();
98  
        return sched_->run();
104  
    }
99  
    }
105  

100  

106  
    /** Process at most one pending work item.
101  
    /** Process at most one pending work item.
107  

102  

108  
        This function blocks until one work item has been executed
103  
        This function blocks until one work item has been executed
109  
        or `stop()` is called. The context is stopped when there
104  
        or `stop()` is called. The context is stopped when there
110  
        is no more outstanding work.
105  
        is no more outstanding work.
111  

106  

112  
        @note The context must be restarted with `restart()` before
107  
        @note The context must be restarted with `restart()` before
113  
            calling this function again after it returns.
108  
            calling this function again after it returns.
114  

109  

115  
        @return The number of handlers executed (0 or 1).
110  
        @return The number of handlers executed (0 or 1).
116  
    */
111  
    */
117 -
    std::size_t
112 +
    std::size_t run_one()
118 -
    run_one()
 
119  
    {
113  
    {
120  
        return sched_->run_one();
114  
        return sched_->run_one();
121  
    }
115  
    }
122  

116  

123  
    /** Process work items for the specified duration.
117  
    /** Process work items for the specified duration.
124  

118  

125  
        This function blocks until work items have been executed for
119  
        This function blocks until work items have been executed for
126  
        the specified duration, or `stop()` is called. The context
120  
        the specified duration, or `stop()` is called. The context
127  
        is stopped when there is no more outstanding work.
121  
        is stopped when there is no more outstanding work.
128  

122  

129  
        @note The context must be restarted with `restart()` before
123  
        @note The context must be restarted with `restart()` before
130  
            calling this function again after it returns.
124  
            calling this function again after it returns.
131  

125  

132  
        @param rel_time The duration for which to process work.
126  
        @param rel_time The duration for which to process work.
133  

127  

134  
        @return The number of handlers executed.
128  
        @return The number of handlers executed.
135  
    */
129  
    */
136  
    template<class Rep, class Period>
130  
    template<class Rep, class Period>
137 -
    std::size_t
131 +
    std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
138 -
    run_for(std::chrono::duration<Rep, Period> const& rel_time)
 
139  
    {
132  
    {
140  
        return run_until(std::chrono::steady_clock::now() + rel_time);
133  
        return run_until(std::chrono::steady_clock::now() + rel_time);
141  
    }
134  
    }
142  

135  

143  
    /** Process work items until the specified time.
136  
    /** Process work items until the specified time.
144  

137  

145  
        This function blocks until the specified time is reached
138  
        This function blocks until the specified time is reached
146  
        or `stop()` is called. The context is stopped when there
139  
        or `stop()` is called. The context is stopped when there
147  
        is no more outstanding work.
140  
        is no more outstanding work.
148  

141  

149  
        @note The context must be restarted with `restart()` before
142  
        @note The context must be restarted with `restart()` before
150  
            calling this function again after it returns.
143  
            calling this function again after it returns.
151  

144  

152  
        @param abs_time The time point until which to process work.
145  
        @param abs_time The time point until which to process work.
153  

146  

154  
        @return The number of handlers executed.
147  
        @return The number of handlers executed.
155  
    */
148  
    */
156  
    template<class Clock, class Duration>
149  
    template<class Clock, class Duration>
157  
    std::size_t
150  
    std::size_t
158  
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
151  
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
159  
    {
152  
    {
160  
        std::size_t n = 0;
153  
        std::size_t n = 0;
161  
        while (run_one_until(abs_time))
154  
        while (run_one_until(abs_time))
162  
            if (n != (std::numeric_limits<std::size_t>::max)())
155  
            if (n != (std::numeric_limits<std::size_t>::max)())
163  
                ++n;
156  
                ++n;
164  
        return n;
157  
        return n;
165  
    }
158  
    }
166  

159  

167  
    /** Process at most one work item for the specified duration.
160  
    /** Process at most one work item for the specified duration.
168  

161  

169  
        This function blocks until one work item has been executed,
162  
        This function blocks until one work item has been executed,
170  
        the specified duration has elapsed, or `stop()` is called.
163  
        the specified duration has elapsed, or `stop()` is called.
171  
        The context is stopped when there is no more outstanding work.
164  
        The context is stopped when there is no more outstanding work.
172  

165  

173  
        @note The context must be restarted with `restart()` before
166  
        @note The context must be restarted with `restart()` before
174  
            calling this function again after it returns.
167  
            calling this function again after it returns.
175  

168  

176  
        @param rel_time The duration for which the call may block.
169  
        @param rel_time The duration for which the call may block.
177  

170  

178  
        @return The number of handlers executed (0 or 1).
171  
        @return The number of handlers executed (0 or 1).
179  
    */
172  
    */
180  
    template<class Rep, class Period>
173  
    template<class Rep, class Period>
181 -
    std::size_t
174 +
    std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
182 -
    run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
 
183  
    {
175  
    {
184  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
176  
        return run_one_until(std::chrono::steady_clock::now() + rel_time);
185  
    }
177  
    }
186  

178  

187  
    /** Process at most one work item until the specified time.
179  
    /** Process at most one work item until the specified time.
188  

180  

189  
        This function blocks until one work item has been executed,
181  
        This function blocks until one work item has been executed,
190  
        the specified time is reached, or `stop()` is called.
182  
        the specified time is reached, or `stop()` is called.
191  
        The context is stopped when there is no more outstanding work.
183  
        The context is stopped when there is no more outstanding work.
192  

184  

193  
        @note The context must be restarted with `restart()` before
185  
        @note The context must be restarted with `restart()` before
194  
            calling this function again after it returns.
186  
            calling this function again after it returns.
195  

187  

196  
        @param abs_time The time point until which the call may block.
188  
        @param abs_time The time point until which the call may block.
197  

189  

198  
        @return The number of handlers executed (0 or 1).
190  
        @return The number of handlers executed (0 or 1).
199  
    */
191  
    */
200  
    template<class Clock, class Duration>
192  
    template<class Clock, class Duration>
201  
    std::size_t
193  
    std::size_t
202  
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
194  
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
203  
    {
195  
    {
204  
        typename Clock::time_point now = Clock::now();
196  
        typename Clock::time_point now = Clock::now();
205  
        while (now < abs_time)
197  
        while (now < abs_time)
206  
        {
198  
        {
207  
            auto rel_time = abs_time - now;
199  
            auto rel_time = abs_time - now;
208  
            if (rel_time > std::chrono::seconds(1))
200  
            if (rel_time > std::chrono::seconds(1))
209  
                rel_time = std::chrono::seconds(1);
201  
                rel_time = std::chrono::seconds(1);
210  

202  

211  
            std::size_t s = sched_->wait_one(
203  
            std::size_t s = sched_->wait_one(
212 -
                static_cast<long>(std::chrono::duration_cast<
204 +
                static_cast<long>(
213 -
                    std::chrono::microseconds>(rel_time).count()));
205 +
                    std::chrono::duration_cast<std::chrono::microseconds>(
 
206 +
                        rel_time)
 
207 +
                        .count()));
214  

208  

215  
            if (s || stopped())
209  
            if (s || stopped())
216  
                return s;
210  
                return s;
217  

211  

218  
            now = Clock::now();
212  
            now = Clock::now();
219  
        }
213  
        }
220  
        return 0;
214  
        return 0;
221  
    }
215  
    }
222  

216  

223  
    /** Process all ready work items without blocking.
217  
    /** Process all ready work items without blocking.
224  

218  

225  
        This function executes all work items that are ready to run
219  
        This function executes all work items that are ready to run
226  
        without blocking for more work. The context is stopped
220  
        without blocking for more work. The context is stopped
227  
        when there is no more outstanding work.
221  
        when there is no more outstanding work.
228  

222  

229  
        @note The context must be restarted with `restart()` before
223  
        @note The context must be restarted with `restart()` before
230  
            calling this function again after it returns.
224  
            calling this function again after it returns.
231  

225  

232  
        @return The number of handlers executed.
226  
        @return The number of handlers executed.
233  
    */
227  
    */
234 -
    std::size_t
228 +
    std::size_t poll()
235 -
    poll()
 
236  
    {
229  
    {
237  
        return sched_->poll();
230  
        return sched_->poll();
238  
    }
231  
    }
239  

232  

240  
    /** Process at most one ready work item without blocking.
233  
    /** Process at most one ready work item without blocking.
241  

234  

242  
        This function executes at most one work item that is ready
235  
        This function executes at most one work item that is ready
243  
        to run without blocking for more work. The context is
236  
        to run without blocking for more work. The context is
244  
        stopped when there is no more outstanding work.
237  
        stopped when there is no more outstanding work.
245  

238  

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

241  

249  
        @return The number of handlers executed (0 or 1).
242  
        @return The number of handlers executed (0 or 1).
250  
    */
243  
    */
251 -
    std::size_t
244 +
    std::size_t poll_one()
252 -
    poll_one()
 
253  
    {
245  
    {
254  
        return sched_->poll_one();
246  
        return sched_->poll_one();
255  
    }
247  
    }
256  

248  

257  
protected:
249  
protected:
258  
    /** Default constructor.
250  
    /** Default constructor.
259  

251  

260  
        Derived classes must set sched_ in their constructor body.
252  
        Derived classes must set sched_ in their constructor body.
261  
    */
253  
    */
262 -
    basic_io_context()
254 +
    basic_io_context() : capy::execution_context(this), sched_(nullptr) {}
263 -
        : capy::execution_context(this)
 
264 -
        , sched_(nullptr)
 
265 -
    {
 
266 -
    }
 
267  

255  

268  
    detail::scheduler* sched_;
256  
    detail::scheduler* sched_;
269  
};
257  
};
270  

258  

271  
/** An executor for dispatching work to an I/O context.
259  
/** An executor for dispatching work to an I/O context.
272  

260  

273  
    The executor provides the interface for posting work items and
261  
    The executor provides the interface for posting work items and
274  
    dispatching coroutines to the associated context. It satisfies
262  
    dispatching coroutines to the associated context. It satisfies
275  
    the `capy::Executor` concept.
263  
    the `capy::Executor` concept.
276  

264  

277  
    Executors are lightweight handles that can be copied and compared
265  
    Executors are lightweight handles that can be copied and compared
278  
    for equality. Two executors compare equal if they refer to the
266  
    for equality. Two executors compare equal if they refer to the
279  
    same context.
267  
    same context.
280  

268  

281  
    @par Thread Safety
269  
    @par Thread Safety
282  
    Distinct objects: Safe.@n
270  
    Distinct objects: Safe.@n
283  
    Shared objects: Safe.
271  
    Shared objects: Safe.
284  
*/
272  
*/
285  
class basic_io_context::executor_type
273  
class basic_io_context::executor_type
286  
{
274  
{
287  
    basic_io_context* ctx_ = nullptr;
275  
    basic_io_context* ctx_ = nullptr;
288  

276  

289  
public:
277  
public:
290  
    /** Default constructor.
278  
    /** Default constructor.
291  

279  

292  
        Constructs an executor not associated with any context.
280  
        Constructs an executor not associated with any context.
293  
    */
281  
    */
294  
    executor_type() = default;
282  
    executor_type() = default;
295  

283  

296  
    /** Construct an executor from a context.
284  
    /** Construct an executor from a context.
297  

285  

298  
        @param ctx The context to associate with this executor.
286  
        @param ctx The context to associate with this executor.
299  
    */
287  
    */
300 -
    explicit
288 +
    explicit executor_type(basic_io_context& ctx) noexcept : ctx_(&ctx) {}
301 -
    executor_type(basic_io_context& ctx) noexcept
 
302 -
        : ctx_(&ctx)
 
303 -
    {
 
304 -
    }
 
305  

289  

306  
    /** Return a reference to the associated execution context.
290  
    /** Return a reference to the associated execution context.
307  

291  

308  
        @return Reference to the context.
292  
        @return Reference to the context.
309  
    */
293  
    */
310 -
    basic_io_context&
294 +
    basic_io_context& context() const noexcept
311 -
    context() const noexcept
 
312  
    {
295  
    {
313  
        return *ctx_;
296  
        return *ctx_;
314  
    }
297  
    }
315  

298  

316  
    /** Check if the current thread is running this executor's context.
299  
    /** Check if the current thread is running this executor's context.
317  

300  

318  
        @return `true` if `run()` is being called on this thread.
301  
        @return `true` if `run()` is being called on this thread.
319  
    */
302  
    */
320 -
    bool
303 +
    bool running_in_this_thread() const noexcept
321 -
    running_in_this_thread() const noexcept
 
322  
    {
304  
    {
323  
        return ctx_->sched_->running_in_this_thread();
305  
        return ctx_->sched_->running_in_this_thread();
324  
    }
306  
    }
325  

307  

326  
    /** Informs the executor that work is beginning.
308  
    /** Informs the executor that work is beginning.
327  

309  

328  
        Must be paired with `on_work_finished()`.
310  
        Must be paired with `on_work_finished()`.
329  
    */
311  
    */
330 -
    void
312 +
    void on_work_started() const noexcept
331 -
    on_work_started() const noexcept
 
332  
    {
313  
    {
333 -
        ctx_->sched_->on_work_started();
314 +
        ctx_->sched_->work_started();
334  
    }
315  
    }
335  

316  

336  
    /** Informs the executor that work has completed.
317  
    /** Informs the executor that work has completed.
337  

318  

338  
        @par Preconditions
319  
        @par Preconditions
339  
        A preceding call to `on_work_started()` on an equal executor.
320  
        A preceding call to `on_work_started()` on an equal executor.
340  
    */
321  
    */
341 -
    void
322 +
    void on_work_finished() const noexcept
342 -
    on_work_finished() const noexcept
 
343  
    {
323  
    {
344 -
        ctx_->sched_->on_work_finished();
324 +
        ctx_->sched_->work_finished();
345  
    }
325  
    }
346  

326  

347  
    /** Dispatch a coroutine handle.
327  
    /** Dispatch a coroutine handle.
348  

328  

349  
        Returns a handle for symmetric transfer. If called from
329  
        Returns a handle for symmetric transfer. If called from
350  
        within `run()`, returns `h`. Otherwise posts the coroutine
330  
        within `run()`, returns `h`. Otherwise posts the coroutine
351  
        for later execution and returns `std::noop_coroutine()`.
331  
        for later execution and returns `std::noop_coroutine()`.
352  

332  

353  
        @param h The coroutine handle to dispatch.
333  
        @param h The coroutine handle to dispatch.
354  

334  

355  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
335  
        @return A handle for symmetric transfer or `std::noop_coroutine()`.
356  
    */
336  
    */
357 -
    std::coroutine_handle<>
337 +
    std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
358 -
    dispatch(std::coroutine_handle<> h) const
 
359  
    {
338  
    {
360  
        if (running_in_this_thread())
339  
        if (running_in_this_thread())
361  
            return h;
340  
            return h;
362  
        ctx_->sched_->post(h);
341  
        ctx_->sched_->post(h);
363  
        return std::noop_coroutine();
342  
        return std::noop_coroutine();
364  
    }
343  
    }
365  

344  

366  
    /** Post a coroutine for deferred execution.
345  
    /** Post a coroutine for deferred execution.
367  

346  

368  
        The coroutine will be resumed during a subsequent call to
347  
        The coroutine will be resumed during a subsequent call to
369  
        `run()`.
348  
        `run()`.
370  

349  

371  
        @param h The coroutine handle to post.
350  
        @param h The coroutine handle to post.
372  
    */
351  
    */
373 -
    void
352 +
    void post(std::coroutine_handle<> h) const
374 -
    post(std::coroutine_handle<> h) const
 
375  
    {
353  
    {
376  
        ctx_->sched_->post(h);
354  
        ctx_->sched_->post(h);
377  
    }
355  
    }
378  

356  

379  
    /** Compare two executors for equality.
357  
    /** Compare two executors for equality.
380  

358  

381  
        @return `true` if both executors refer to the same context.
359  
        @return `true` if both executors refer to the same context.
382  
    */
360  
    */
383 -
    bool
361 +
    bool operator==(executor_type const& other) const noexcept
384 -
    operator==(executor_type const& other) const noexcept
 
385  
    {
362  
    {
386  
        return ctx_ == other.ctx_;
363  
        return ctx_ == other.ctx_;
387  
    }
364  
    }
388  

365  

389  
    /** Compare two executors for inequality.
366  
    /** Compare two executors for inequality.
390  

367  

391  
        @return `true` if the executors refer to different contexts.
368  
        @return `true` if the executors refer to different contexts.
392  
    */
369  
    */
393 -
    bool
370 +
    bool operator!=(executor_type const& other) const noexcept
394 -
    operator!=(executor_type const& other) const noexcept
 
395  
    {
371  
    {
396  
        return ctx_ != other.ctx_;
372  
        return ctx_ != other.ctx_;
397  
    }
373  
    }
398  
};
374  
};
399  

375  

400 -
inline
376 +
inline basic_io_context::executor_type
401 -
basic_io_context::executor_type
377 +
basic_io_context::get_executor() const noexcept
402 -
basic_io_context::
 
403 -
get_executor() const noexcept
 
404  
{
378  
{
405  
    return executor_type(const_cast<basic_io_context&>(*this));
379  
    return executor_type(const_cast<basic_io_context&>(*this));
406  
}
380  
}
407  

381  

408  
} // namespace boost::corosio
382  
} // namespace boost::corosio
409  

383  

410  
#endif // BOOST_COROSIO_BASIC_IO_CONTEXT_HPP
384  
#endif // BOOST_COROSIO_BASIC_IO_CONTEXT_HPP