src/corosio/src/io_context.cpp

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