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
54  
    get_executor() const noexcept;
54  
    get_executor() const noexcept;
55  

55  

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

57  

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

66  

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

68  

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

77  

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

79  

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

88  

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

90  

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

94  

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

97  

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

105  

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

107  

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

111  

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

114  

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

122  

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

124  

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

128  

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

131  

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

133  

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

142  

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

144  

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

148  

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

151  

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

153  

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

166  

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

168  

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

172  

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

175  

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

177  

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

186  

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

188  

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

192  

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

195  

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

197  

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

210  

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

214  

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

217  

218  
            now = Clock::now();
218  
            now = Clock::now();
219  
        }
219  
        }
220  
        return 0;
220  
        return 0;
221  
    }
221  
    }
222  

222  

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

224  

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

228  

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

231  

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

239  

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

241  

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

245  

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

248  

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

256  

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

259  

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

267  

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

270  

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

272  

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

276  

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

280  

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

288  

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

291  

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

295  

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

297  

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

305  

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

307  

308  
        @return Reference to the context.
308  
        @return Reference to the context.
309  
    */
309  
    */
310  
    basic_io_context&
310  
    basic_io_context&
311  
    context() const noexcept
311  
    context() const noexcept
312  
    {
312  
    {
313  
        return *ctx_;
313  
        return *ctx_;
314  
    }
314  
    }
315  

315  

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

317  

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

325  

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

327  

328  
        Must be paired with `on_work_finished()`.
328  
        Must be paired with `on_work_finished()`.
329  
    */
329  
    */
330  
    void
330  
    void
331  
    on_work_started() const noexcept
331  
    on_work_started() const noexcept
332  
    {
332  
    {
333  
        ctx_->sched_->on_work_started();
333  
        ctx_->sched_->on_work_started();
334  
    }
334  
    }
335  

335  

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

337  

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

346  

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

348  

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

352  

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

354  

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

365  

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

367  

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

370  

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

378  

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

380  

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

388  

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

390  

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

399  

400  
inline
400  
inline
401  
basic_io_context::executor_type
401  
basic_io_context::executor_type
402  
basic_io_context::
402  
basic_io_context::
403  
get_executor() const noexcept
403  
get_executor() const noexcept
404  
{
404  
{
405  
    return executor_type(const_cast<basic_io_context&>(*this));
405  
    return executor_type(const_cast<basic_io_context&>(*this));
406  
}
406  
}
407  

407  

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

409  

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