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 -
    void stop()
 
61 -
    {
 
62 -
        sched_->stop();
 
63 -
    }
 
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 -
    bool stopped() const noexcept
 
71 -
    {
 
72 -
        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 -
    void restart()
 
81 -
    {
 
82 -
        sched_->restart();
 
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 -
    std::size_t run()
 
97 -
    {
 
98 -
        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 -
    std::size_t run_one()
 
113 -
    {
 
114 -
        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 -
    std::size_t run_for(std::chrono::duration<Rep, Period> const& rel_time)
 
132 -
    {
 
133 -
        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 -
    run_until(std::chrono::time_point<Clock, Duration> const& abs_time)
 
152 -
    {
 
153 -
        std::size_t n = 0;
 
154 -
        while (run_one_until(abs_time))
 
155 -
            if (n != (std::numeric_limits<std::size_t>::max)())
 
156 -
                ++n;
 
157 -
        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 -
    std::size_t run_one_for(std::chrono::duration<Rep, Period> const& rel_time)
 
175 -
    {
 
176 -
        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 -
    run_one_until(std::chrono::time_point<Clock, Duration> const& abs_time)
 
195 -
    {
 
196 -
        typename Clock::time_point now = Clock::now();
 
197 -
        while (now < abs_time)
 
198 -
        {
 
199 -
            auto rel_time = abs_time - now;
 
200 -
            if (rel_time > std::chrono::seconds(1))
 
201 -
                rel_time = std::chrono::seconds(1);
 
202 -

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

 
209 -
            if (s || stopped())
 
210 -
                return s;
 
211 -

 
212 -
            now = Clock::now();
 
213 -
        }
 
214 -
        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 -
    std::size_t poll()
 
229 -
    {
 
230 -
        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 -
    std::size_t poll_one()
 
245 -
    {
 
246 -
        return sched_->poll_one();
 
247 -
    }
 
248 -

 
249 -
protected:
 
250 -
    /** Default constructor.
 
251 -

 
252 -
        Derived classes must set sched_ in their constructor body.
 
253 -
    */
 
254 -
    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 -
    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 -
    basic_io_context& context() const noexcept
 
295 -
    {
 
296 -
        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 -
    bool running_in_this_thread() const noexcept
 
304 -
    {
 
305 -
        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 -
    void on_work_started() const noexcept
 
313 -
    {
 
314 -
        ctx_->sched_->work_started();
 
315 -
    }
 
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 -
    void on_work_finished() const noexcept
 
323 -
    {
 
324 -
        ctx_->sched_->work_finished();
 
325 -
    }
 
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 -
    std::coroutine_handle<> dispatch(std::coroutine_handle<> h) const
 
338 -
    {
 
339 -
        if (running_in_this_thread())
 
340 -
            return h;
 
341 -
        ctx_->sched_->post(h);
 
342 -
        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 -
    void post(std::coroutine_handle<> h) const
 
353 -
    {
 
354 -
        ctx_->sched_->post(h);
 
355 -
    }
 
356 -

 
357 -
    /** Compare two executors for equality.
 
358 -

 
359 -
        @return `true` if both executors refer to the same context.
 
360 -
    */
 
361 -
    bool operator==(executor_type const& other) const noexcept
 
362 -
    {
 
363 -
        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 -
basic_io_context::get_executor() const noexcept
 
378 -
{
 
379 -
    return executor_type(const_cast<basic_io_context&>(*this));
 
380 -
}
 
381 -

 
382 -
} // namespace boost::corosio
 
383 -

 
384 -
#endif // BOOST_COROSIO_BASIC_IO_CONTEXT_HPP