TLA Line data 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 HIT 2322 : ~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 MIS 0 : 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 HIT 612 : get_scheduler(capy::execution_context& ctx)
144 : {
145 612 : auto* sched = ctx.find_service<scheduler>();
146 612 : if (!sched)
147 MIS 0 : throw_logic_error("no scheduler installed");
148 HIT 612 : return *sched;
149 : }
150 :
151 : } // namespace boost::corosio::detail
152 :
153 : #endif
|