include/boost/corosio/detail/scheduler.hpp

71.4% Lines (5 / 7, 1 excl) 66.7% Functions (2 / 3)
scheduler.hpp
f(x) Functions (3)
Line TLA Hits 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_DETAIL_SCHEDULER_HPP
13 #define BOOST_COROSIO_DETAIL_SCHEDULER_HPP
14
15 #include <boost/corosio/detail/config.hpp>
16 #include <boost/corosio/detail/except.hpp>
17
18 #include <boost/capy/continuation.hpp>
19 #include <boost/capy/ex/execution_context.hpp>
20
21 #include <coroutine>
22 #include <cstddef>
23 #include <system_error>
24
25 namespace boost::corosio::detail {
26
27 class scheduler_op;
28
29 /** Define the abstract interface for the event loop scheduler.
30
31 Concrete backends (epoll, IOCP, kqueue, select) derive from
32 this to implement the reactor/proactor event loop. The
33 @ref io_context delegates all scheduling operations here.
34
35 The scheduler is a registry service keyed under this abstract
36 type, so services created on first use can locate it without
37 naming a concrete backend.
38
39 @see io_context
40 */
41 struct BOOST_COROSIO_DECL scheduler
42 : capy::execution_context::service
43 {
44 using key_type = scheduler;
45
46 2255x ~scheduler() override = default;
47
48 /// Post a coroutine handle for deferred execution.
49 virtual void post(std::coroutine_handle<>) const = 0;
50
51 /// Post a scheduler operation for deferred execution.
52 virtual void post(scheduler_op*) const = 0;
53
54 /// Post a continuation for deferred execution (zero-allocation).
55 virtual void post(capy::continuation&) const = 0;
56
57 /// Increment the outstanding work count.
58 virtual void work_started() noexcept = 0;
59
60 /// Decrement the outstanding work count.
61 virtual void work_finished() noexcept = 0;
62
63 /// Check if the calling thread is running the event loop.
64 virtual bool running_in_this_thread() const noexcept = 0;
65
66 /// Signal the event loop to stop.
67 virtual void stop() = 0;
68
69 /// Check if the event loop has been stopped.
70 virtual bool stopped() const noexcept = 0;
71
72 /// Reset the stopped state so `run()` can be called again.
73 virtual void restart() = 0;
74
75 /// Run the event loop, blocking until all work completes.
76 virtual std::size_t run() = 0;
77
78 /// Run one handler, blocking until one completes.
79 virtual std::size_t run_one() = 0;
80
81 /** Run one handler, blocking up to @p usec microseconds.
82
83 @param usec Maximum wait time in microseconds.
84
85 @return The number of handlers executed (0 or 1).
86 */
87 virtual std::size_t wait_one(long usec) = 0;
88
89 /// Run all ready handlers without blocking.
90 virtual std::size_t poll() = 0;
91
92 /// Run at most one ready handler without blocking.
93 virtual std::size_t poll_one() = 0;
94
95 /** Register the read end of the POSIX signal self-pipe.
96
97 Called once (by the first signal_set to register a signal) so the
98 backend's event loop watches @p read_fd for readability. When the
99 pipe becomes readable the backend drains it and calls
100 `posix_signal_service::deliver_signal` for each pending signal, in
101 normal thread context. This keeps the C signal handler
102 async-signal-safe: it only writes the signal number to the pipe.
103
104 POSIX backends override this; the default is a no-op (Windows/IOCP
105 uses synchronous C-runtime signal handling instead).
106
107 @param read_fd The read end of the global signal self-pipe.
108
109 @return The error code, empty on success.
110 */
111 [[nodiscard]] virtual std::error_code
112 ✗ register_signal_reader([[maybe_unused]] int read_fd)
113 {
114 − return {}; // LCOV_EXCL_LINE POSIX overrides; the only caller never runs on IOCP
115 }
116
117 /// Decomposed threading configuration applied via @ref configure_threading.
118 struct threading_config
119 {
120 /// Scheduler mutex/condvar enabled. Off only in the `unsafe` tier.
121 bool scheduler_locking = true;
122 /// Per-descriptor (reactor) or ring (uring) I/O lock enabled.
123 /// Off in the `unsafe_io` and `unsafe` tiers.
124 bool reactor_io_locking = true;
125 /// A single run thread is guaranteed (a lockless tier): elide
126 /// inter-run-thread wakeups.
127 bool one_thread = false;
128 };
129
130 /// True in the fully-lockless (`unsafe`) tier. The resolver and POSIX
131 /// file services gate their `operation_not_supported` result on this.
132 virtual bool scheduler_locking_disabled() const noexcept = 0;
133
134 /// Apply @ref threading_config.
135 virtual void configure_threading(threading_config) noexcept = 0;
136 };
137
138 /** Return the scheduler registered with the context.
139
140 @throws std::logic_error If the context has no backend installed.
141 */
142 inline scheduler&
143 600x get_scheduler(capy::execution_context& ctx)
144 {
145 600x auto* sched = ctx.find_service<scheduler>();
146 600x if (!sched)
147 ✗ throw_logic_error("no scheduler installed");
148 600x return *sched;
149 }
150
151 } // namespace boost::corosio::detail
152
153 #endif
154