src/corosio/src/io_context.cpp

46.3% Lines (38/82) 53.8% List of functions (7/13)
io_context.cpp
f(x) Functions (13)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
3 // Copyright (c) 2026 Michael Vandeberg
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #include <boost/corosio/io_context.hpp>
12 #include <boost/corosio/backend.hpp>
13 #include <boost/corosio/detail/thread_pool.hpp>
14
15 #include <algorithm>
16 #include <stdexcept>
17 #include <thread>
18
19 #if BOOST_COROSIO_HAS_EPOLL
20 #include <boost/corosio/native/detail/epoll/epoll_types.hpp>
21 #endif
22
23 #if BOOST_COROSIO_HAS_SELECT
24 #include <boost/corosio/native/detail/select/select_types.hpp>
25 #endif
26
27 #if BOOST_COROSIO_HAS_KQUEUE
28 #include <boost/corosio/native/detail/kqueue/kqueue_types.hpp>
29 #endif
30
31 #if BOOST_COROSIO_HAS_IOCP
32 #include <boost/corosio/native/detail/iocp/win_scheduler.hpp>
33 #include <boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp>
34 #include <boost/corosio/native/detail/iocp/win_udp_service.hpp>
35 #include <boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp>
36 #include <boost/corosio/native/detail/iocp/win_signals.hpp>
37 #include <boost/corosio/native/detail/iocp/win_file_service.hpp>
38 #include <boost/corosio/native/detail/iocp/win_random_access_file_service.hpp>
39 #endif
40
41 namespace boost::corosio {
42
43 #if BOOST_COROSIO_HAS_EPOLL
44 detail::scheduler&
45 373x epoll_t::construct(capy::execution_context& ctx, unsigned concurrency_hint)
46 {
47 746x auto& sched = ctx.make_service<detail::epoll_scheduler>(
48 373x static_cast<int>(concurrency_hint));
49
50 373x ctx.make_service<detail::epoll_tcp_service>();
51 373x ctx.make_service<detail::epoll_tcp_acceptor_service>();
52 373x ctx.make_service<detail::epoll_udp_service>();
53 373x ctx.make_service<detail::epoll_local_stream_service>();
54 373x ctx.make_service<detail::epoll_local_stream_acceptor_service>();
55 373x ctx.make_service<detail::epoll_local_datagram_service>();
56
57 373x return sched;
58 }
59 #endif
60
61 #if BOOST_COROSIO_HAS_SELECT
62 detail::scheduler&
63 246x select_t::construct(capy::execution_context& ctx, unsigned concurrency_hint)
64 {
65 492x auto& sched = ctx.make_service<detail::select_scheduler>(
66 246x static_cast<int>(concurrency_hint));
67
68 246x ctx.make_service<detail::select_tcp_service>();
69 246x ctx.make_service<detail::select_tcp_acceptor_service>();
70 246x ctx.make_service<detail::select_udp_service>();
71 246x ctx.make_service<detail::select_local_stream_service>();
72 246x ctx.make_service<detail::select_local_stream_acceptor_service>();
73 246x ctx.make_service<detail::select_local_datagram_service>();
74
75 246x return sched;
76 }
77 #endif
78
79 #if BOOST_COROSIO_HAS_KQUEUE
80 detail::scheduler&
81 kqueue_t::construct(capy::execution_context& ctx, unsigned concurrency_hint)
82 {
83 auto& sched = ctx.make_service<detail::kqueue_scheduler>(
84 static_cast<int>(concurrency_hint));
85
86 ctx.make_service<detail::kqueue_tcp_service>();
87 ctx.make_service<detail::kqueue_tcp_acceptor_service>();
88 ctx.make_service<detail::kqueue_udp_service>();
89 ctx.make_service<detail::kqueue_local_stream_service>();
90 ctx.make_service<detail::kqueue_local_stream_acceptor_service>();
91 ctx.make_service<detail::kqueue_local_datagram_service>();
92
93 return sched;
94 }
95 #endif
96
97 #if BOOST_COROSIO_HAS_IOCP
98 detail::scheduler&
99 iocp_t::construct(capy::execution_context& ctx, unsigned concurrency_hint)
100 {
101 auto& sched = ctx.make_service<detail::win_scheduler>(
102 static_cast<int>(concurrency_hint));
103
104 auto& tcp_svc = ctx.make_service<detail::win_tcp_service>();
105 ctx.make_service<detail::win_tcp_acceptor_service>(tcp_svc);
106 ctx.make_service<detail::win_udp_service>();
107 auto& local_svc =
108 ctx.make_service<detail::win_local_stream_service>(tcp_svc);
109 ctx.make_service<detail::win_local_stream_acceptor_service>(local_svc);
110 ctx.make_service<detail::win_signals>();
111 ctx.make_service<detail::win_file_service>();
112 ctx.make_service<detail::win_random_access_file_service>();
113
114 return sched;
115 }
116 #endif
117
118 namespace {
119
120 // Pre-create services that must exist before construct() runs.
121 void
122 pre_create_services(
123 capy::execution_context& ctx,
124 io_context_options const& opts)
125 {
126 #if BOOST_COROSIO_POSIX
127 if (opts.thread_pool_size < 1)
128 throw std::invalid_argument(
129 "thread_pool_size must be at least 1");
130 // Pre-create the shared thread pool with the configured size.
131 // This must happen before construct() because the scheduler
132 // constructor creates file and resolver services that call
133 // get_or_create_pool(), which would create a 1-thread pool.
134 if (opts.thread_pool_size != 1)
135 ctx.make_service<detail::thread_pool>(opts.thread_pool_size);
136 #endif
137
138 (void)ctx;
139 (void)opts;
140 }
141
142 // Apply runtime tuning to the scheduler after construction.
143 //
144 // Concurrency-hint heuristic for budget defaults: when the io_context is
145 // constructed with concurrency_hint > 1 AND the user has not customized
146 // the budget settings (i.e. they remain at the struct defaults), we
147 // disable the inline-completion fast path. Multi-thread workloads
148 // benefit from "always-post" because cross-thread work-stealing wins
149 // over chained dispatch on the originating thread. Single-thread (or
150 // any custom budget) keeps the user/library setting unchanged.
151 void
152 apply_scheduler_options(
153 detail::scheduler& sched,
154 io_context_options const& opts,
155 unsigned concurrency_hint)
156 {
157 #if BOOST_COROSIO_HAS_EPOLL || BOOST_COROSIO_HAS_KQUEUE || BOOST_COROSIO_HAS_SELECT
158 // Detect "user kept the defaults" by comparing all three to the
159 // io_context_options-defined struct defaults.
160 io_context_options defaults;
161 bool budget_at_defaults =
162 opts.inline_budget_initial == defaults.inline_budget_initial &&
163 opts.inline_budget_max == defaults.inline_budget_max &&
164 opts.unassisted_budget == defaults.unassisted_budget;
165
166 unsigned init = opts.inline_budget_initial;
167 unsigned max = opts.inline_budget_max;
168 unsigned ua = opts.unassisted_budget;
169
170 if (budget_at_defaults && concurrency_hint > 1)
171 {
172 // Multi-thread default: disable budget (post-everything).
173 init = 0;
174 max = 0;
175 ua = 0;
176 }
177
178 auto& reactor =
179 static_cast<detail::reactor_scheduler&>(sched);
180 reactor.configure_reactor(
181 opts.max_events_per_poll,
182 init,
183 max,
184 ua);
185 if (opts.single_threaded)
186 reactor.configure_single_threaded(true);
187 #endif
188
189 #if BOOST_COROSIO_HAS_IOCP
190 auto& iocp_sched = static_cast<detail::win_scheduler&>(sched);
191 iocp_sched.configure_iocp(opts.gqcs_timeout_ms);
192 if (opts.single_threaded)
193 iocp_sched.configure_single_threaded(true);
194 #endif
195
196 (void)sched;
197 (void)opts;
198 }
199
200 detail::scheduler&
201 127x construct_default(capy::execution_context& ctx, unsigned concurrency_hint)
202 {
203 #if BOOST_COROSIO_HAS_IOCP
204 return iocp_t::construct(ctx, concurrency_hint);
205 #elif BOOST_COROSIO_HAS_EPOLL
206 127x return epoll_t::construct(ctx, concurrency_hint);
207 #elif BOOST_COROSIO_HAS_KQUEUE
208 return kqueue_t::construct(ctx, concurrency_hint);
209 #elif BOOST_COROSIO_HAS_SELECT
210 return select_t::construct(ctx, concurrency_hint);
211 #endif
212 }
213
214 // Tie concurrency_hint == 1 to single_threaded (asio precedent).
215 io_context_options
216 normalize_options(io_context_options opts, unsigned concurrency_hint)
217 {
218 if (concurrency_hint == 1)
219 opts.single_threaded = true;
220 return opts;
221 }
222
223 } // anonymous namespace
224
225 126x io_context::io_context()
226 126x : io_context(std::max(2u, std::thread::hardware_concurrency()))
227 {
228 126x }
229
230 127x io_context::io_context(unsigned concurrency_hint)
231 : capy::execution_context(this)
232 127x , sched_(&construct_default(*this, concurrency_hint))
233 {
234 127x if (concurrency_hint == 1)
235 1x configure_single_threaded_();
236 127x }
237
238 io_context::io_context(
239 io_context_options const& opts_in,
240 unsigned concurrency_hint)
241 : capy::execution_context(this)
242 , sched_(nullptr)
243 {
244 auto opts = normalize_options(opts_in, concurrency_hint);
245 pre_create_services(*this, opts);
246 sched_ = &construct_default(*this, concurrency_hint);
247 apply_scheduler_options(*sched_, opts, concurrency_hint);
248 }
249
250 void
251 io_context::apply_options_pre_(io_context_options const& opts)
252 {
253 pre_create_services(*this, opts);
254 }
255
256 void
257 io_context::apply_options_post_(
258 io_context_options const& opts_in,
259 unsigned concurrency_hint)
260 {
261 auto opts = normalize_options(opts_in, concurrency_hint);
262 apply_scheduler_options(*sched_, opts, concurrency_hint);
263 }
264
265 void
266 1x io_context::configure_single_threaded_()
267 {
268 #if BOOST_COROSIO_HAS_EPOLL || BOOST_COROSIO_HAS_KQUEUE || BOOST_COROSIO_HAS_SELECT
269 1x static_cast<detail::reactor_scheduler&>(*sched_)
270 1x .configure_single_threaded(true);
271 #endif
272 #if BOOST_COROSIO_HAS_IOCP
273 static_cast<detail::win_scheduler&>(*sched_)
274 .configure_single_threaded(true);
275 #endif
276 1x }
277
278 619x io_context::~io_context()
279 {
280 619x shutdown();
281 619x destroy();
282 619x }
283
284 } // namespace boost::corosio
285