include/boost/corosio/native/detail/io_uring/io_uring_dgram_ops.hpp

98.3% Lines (116/118) 100.0% List of functions (10/10)
io_uring_dgram_ops.hpp
f(x) Functions (10)
Function Calls Lines Blocks
boost::corosio::detail::uring_dgram_send_op::uring_dgram_send_op() :57 169x 100.0% 100.0% boost::corosio::detail::uring_dgram_send_op::prepare(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, std::error_code*, unsigned long*, int, boost::corosio::detail::io_uring_scheduler*, std::shared_ptr<void>, boost::corosio::detail::speculative_state*, boost::corosio::buffer_param, unsigned int, sockaddr_storage const&, int, std::stop_token const&) :66 20x 100.0% 100.0% boost::corosio::detail::uring_dgram_send_op::do_prep(boost::corosio::detail::io_uring_op*, io_uring_sqe*) :112 20x 100.0% 100.0% boost::corosio::detail::uring_dgram_send_op::do_cqe(boost::corosio::detail::io_uring_op*, int, unsigned int, boost::corosio::detail::ready_queue&) :120 20x 100.0% 100.0% boost::corosio::detail::uring_dgram_send_op::do_handler(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :129 20x 93.3% 94.0% boost::corosio::detail::uring_dgram_recv_op::uring_dgram_recv_op() :189 169x 100.0% 100.0% boost::corosio::detail::uring_dgram_recv_op::prepare(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, std::error_code*, unsigned long*, int, boost::corosio::detail::io_uring_scheduler*, std::shared_ptr<void>, boost::corosio::detail::speculative_state*, boost::corosio::buffer_param, void*, void (*)(void*, sockaddr_storage const&, unsigned int) noexcept, int, std::stop_token const&) :205 54x 100.0% 100.0% boost::corosio::detail::uring_dgram_recv_op::do_prep(boost::corosio::detail::io_uring_op*, io_uring_sqe*) :267 54x 100.0% 100.0% boost::corosio::detail::uring_dgram_recv_op::do_cqe(boost::corosio::detail::io_uring_op*, int, unsigned int, boost::corosio::detail::ready_queue&) :274 54x 100.0% 100.0% boost::corosio::detail::uring_dgram_recv_op::do_handler(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :285 54x 94.4% 95.0%
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 #ifndef BOOST_COROSIO_NATIVE_DETAIL_IO_URING_IO_URING_DGRAM_OPS_HPP
12 #define BOOST_COROSIO_NATIVE_DETAIL_IO_URING_IO_URING_DGRAM_OPS_HPP
13
14 #include <boost/corosio/detail/platform.hpp>
15
16 #if BOOST_COROSIO_HAS_IO_URING
17
18 #include <liburing.h>
19
20 #include <boost/corosio/detail/dispatch_coro.hpp>
21 #include <boost/corosio/native/detail/io_uring/io_uring_op.hpp>
22 #include <boost/corosio/native/detail/coro_op_complete.hpp>
23 #include <boost/corosio/native/detail/speculative_state.hpp>
24 #include <boost/corosio/native/detail/io_uring/io_uring_socket_ops.hpp>
25 #include <boost/corosio/native/detail/make_err.hpp>
26 #include <boost/capy/error.hpp>
27
28 #include <cstddef>
29 #include <cstdint>
30
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 #include <sys/uio.h>
34
35 namespace boost::corosio::detail {
36
37 /** Datagram send op — connected and unconnected.
38
39 Always uses `IORING_OP_SENDMSG`. In connected mode, `dest_len == 0`
40 and `msg.msg_name == nullptr`. In unconnected mode, `dest_storage`
41 holds the destination and `msg.msg_name` points at it.
42
43 `iovec[io_uring_max_iov]` for scatter/gather: a single datagram
44 can be assembled from N user buffers via `msg.msg_iov`.
45 */
46 struct uring_dgram_send_op : io_uring_op
47 {
48 iovec iovecs[io_uring_max_iov];
49 int iovec_count = 0;
50 msghdr msg{};
51 sockaddr_storage dest_storage{};
52 socklen_t dest_len = 0;
53 int fd = -1;
54 int msg_flags = 0;
55 detail::speculative_state* spec_state = nullptr;
56
57 169x uring_dgram_send_op() noexcept
58 169x : io_uring_op(&do_handler, &do_cqe, &do_prep) {}
59
60 /** Reset and initialize for a new submission.
61
62 Pass `dest_addr_len == 0` for connected-mode datagram sockets
63 (the kernel uses the connected peer); otherwise fill
64 `dest_addr_storage` with the destination address.
65 */
66 20x void prepare(
67 std::coroutine_handle<> handle,
68 capy::executor_ref executor,
69 std::error_code* ec,
70 std::size_t* bytes,
71 int file_descriptor,
72 io_uring_scheduler* scheduler,
73 std::shared_ptr<void> impl,
74 detail::speculative_state* spec,
75 buffer_param buffers,
76 socklen_t dest_addr_len,
77 sockaddr_storage const& dest_addr_storage,
78 int flags,
79 std::stop_token const& token) noexcept
80 {
81 20x h = handle;
82 20x ex = executor;
83 20x ec_out = ec;
84 20x bytes_out = bytes;
85 20x fd = file_descriptor;
86 20x sched_ = scheduler;
87 20x impl_ptr = std::move(impl);
88 20x spec_state = spec;
89 20x res = 0;
90 20x cqe_flags = 0;
91 20x msg_flags = flags;
92
93 20x iovec_count = copy_to_iovec(buffers, iovecs);
94
95 20x msg = {};
96 20x msg.msg_iov = iovecs;
97 20x msg.msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(iovec_count);
98 20x if (dest_addr_len > 0)
99 {
100 10x dest_storage = dest_addr_storage;
101 10x dest_len = dest_addr_len;
102 10x msg.msg_name = &dest_storage;
103 10x msg.msg_namelen = dest_addr_len;
104 }
105 else
106 {
107 10x dest_len = 0;
108 }
109 20x start(token);
110 20x }
111
112 20x static void do_prep(io_uring_op* base, ::io_uring_sqe* sqe) noexcept
113 {
114 20x auto* self = static_cast<uring_dgram_send_op*>(base);
115 20x ::io_uring_prep_sendmsg(
116 20x sqe, self->fd, &self->msg,
117 20x self->msg_flags | MSG_NOSIGNAL);
118 20x }
119
120 20x static void do_cqe(
121 io_uring_op* base, int res, unsigned flags, ready_queue& local) noexcept
122 {
123 20x auto* self = static_cast<uring_dgram_send_op*>(base);
124 20x self->res = res;
125 20x self->cqe_flags = flags;
126 20x local.push(self);
127 20x }
128
129 20x static void do_handler(
130 void* owner, scheduler_op* base,
131 std::uint32_t /*bytes*/, std::uint32_t /*error*/) noexcept
132 {
133 20x auto* self = static_cast<uring_dgram_send_op*>(base);
134 20x if (coro_drain_if_shutdown(owner, self))
135 return;
136
137 20x if (self->sched_)
138 20x self->sched_->reset_inline_budget();
139
140 // Datagram send: no EOF (a 0-byte send is success).
141 38x decode_io_result(
142 self->ec_out,
143 20x self->cancelled.load(std::memory_order_acquire),
144 20x self->res < 0 ? make_err(-self->res) : std::error_code{},
145 /*is_read=*/false, /*bytes=*/0, /*empty_buffer=*/false);
146 20x if (self->bytes_out)
147 20x *self->bytes_out = (self->res >= 0)
148 20x ? static_cast<std::size_t>(self->res) : 0;
149
150 20x if (self->res > 0 && self->spec_state)
151 {
152 // Kernel signalled readiness — restore speculation.
153 18x self->spec_state->on_async_write_ready();
154 }
155
156 20x coro_resume(self);
157 }
158 };
159
160 /** Datagram receive op — connected and unconnected.
161
162 Always uses `IORING_OP_RECVMSG`. In connected mode `msg.msg_name`
163 is null. In unconnected mode `msg.msg_name` points at
164 `source_storage` and the kernel writes the source address there.
165
166 `res == 0` is success (zero-byte datagrams are valid), NOT EOF.
167
168 The `source_writer` callback lets the concrete socket type
169 translate `sockaddr_storage` into `endpoint*` or `local_endpoint*`
170 without the op needing to know which family it is.
171 */
172 struct uring_dgram_recv_op : io_uring_op
173 {
174 iovec iovecs[io_uring_max_iov];
175 int iovec_count = 0;
176 msghdr msg{};
177 sockaddr_storage source_storage{};
178 socklen_t source_len = 0;
179 int fd = -1;
180 int msg_flags = 0;
181 detail::speculative_state* spec_state = nullptr;
182
183 /// Type-erased translator: writes source_storage into the user's
184 /// endpoint output via concrete-class-specific conversion.
185 void* source_writer_ctx = nullptr;
186 void (*source_writer)(
187 void*, sockaddr_storage const&, socklen_t) noexcept = nullptr;
188
189 169x uring_dgram_recv_op() noexcept
190 169x : io_uring_op(&do_handler, &do_cqe, &do_prep) {}
191
192 /** Reset and initialize for a new submission.
193
194 When `source_fn` is non-null, the kernel writes the peer
195 address into `source_storage` and `source_fn(source_ctx, ...)`
196 is invoked from the handler on success to translate it to
197 the user's endpoint output. Connected-mode receivers should
198 pass `source_fn = nullptr`.
199
200 A zero-iovec `buffers` argument yields `iovec_count == 0`;
201 the caller should push the slot onto `completed_ops_`
202 directly (bypassing the kernel) since `recvmsg` would
203 otherwise block forever.
204 */
205 54x void prepare(
206 std::coroutine_handle<> handle,
207 capy::executor_ref executor,
208 std::error_code* ec,
209 std::size_t* bytes,
210 int file_descriptor,
211 io_uring_scheduler* scheduler,
212 std::shared_ptr<void> impl,
213 detail::speculative_state* spec,
214 buffer_param buffers,
215 void* source_ctx,
216 void (*source_fn)(void*, sockaddr_storage const&, socklen_t) noexcept,
217 int flags,
218 std::stop_token const& token) noexcept
219 {
220 54x h = handle;
221 54x ex = executor;
222 54x ec_out = ec;
223 54x bytes_out = bytes;
224 54x fd = file_descriptor;
225 54x sched_ = scheduler;
226 54x impl_ptr = std::move(impl);
227 54x spec_state = spec;
228 54x res = 0;
229 54x cqe_flags = 0;
230 54x msg_flags = flags;
231
232 54x iovec_count = copy_to_iovec(buffers, iovecs);
233
234 54x msg = {};
235 // For the zero-iovec bypass path the caller pushes the slot
236 // straight onto completed_ops_; source_writer must NOT run in
237 // that case (no recvmsg ever happens, source_storage is empty
238 // and would clobber the user's endpoint). Arm the writer only
239 // when there's a real buffer AND the caller asked for it.
240 54x if (iovec_count > 0 && source_fn)
241 {
242 29x msg.msg_iov = iovecs;
243 29x msg.msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(
244 29x iovec_count);
245 29x source_storage = {};
246 29x source_len = sizeof(source_storage);
247 29x msg.msg_name = &source_storage;
248 29x msg.msg_namelen = source_len;
249 29x source_writer_ctx = source_ctx;
250 29x source_writer = source_fn;
251 }
252 else
253 {
254 25x if (iovec_count > 0)
255 {
256 25x msg.msg_iov = iovecs;
257 25x msg.msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(
258 25x iovec_count);
259 }
260 25x source_len = 0;
261 25x source_writer_ctx = nullptr;
262 25x source_writer = nullptr;
263 }
264 54x start(token);
265 54x }
266
267 54x static void do_prep(io_uring_op* base, ::io_uring_sqe* sqe) noexcept
268 {
269 54x auto* self = static_cast<uring_dgram_recv_op*>(base);
270 54x ::io_uring_prep_recvmsg(
271 54x sqe, self->fd, &self->msg, self->msg_flags);
272 54x }
273
274 54x static void do_cqe(
275 io_uring_op* base, int res, unsigned flags, ready_queue& local) noexcept
276 {
277 54x auto* self = static_cast<uring_dgram_recv_op*>(base);
278 54x self->res = res;
279 54x self->cqe_flags = flags;
280 // recvmsg writes the actual source addrlen back into msg.msg_namelen.
281 54x self->source_len = self->msg.msg_namelen;
282 54x local.push(self);
283 54x }
284
285 54x static void do_handler(
286 void* owner, scheduler_op* base,
287 std::uint32_t /*bytes*/, std::uint32_t /*error*/) noexcept
288 {
289 54x auto* self = static_cast<uring_dgram_recv_op*>(base);
290 54x if (coro_drain_if_shutdown(owner, self))
291 return;
292
293 54x if (self->sched_)
294 54x self->sched_->reset_inline_budget();
295
296 // Datagram recv: a 0-byte datagram is success, not EOF — is_read
297 // stays false so the shared decode never maps it to end_of_file.
298 99x decode_io_result(
299 self->ec_out,
300 54x self->cancelled.load(std::memory_order_acquire),
301 54x self->res < 0 ? make_err(-self->res) : std::error_code{},
302 /*is_read=*/false, /*bytes=*/0, /*empty_buffer=*/false);
303 54x if (self->bytes_out)
304 54x *self->bytes_out = (self->res >= 0)
305 54x ? static_cast<std::size_t>(self->res) : 0;
306
307 54x if (self->res > 0 && self->spec_state)
308 {
309 // Kernel signalled readiness — restore speculation.
310 45x self->spec_state->on_async_read_ready();
311 }
312
313 // Translate source storage into user's endpoint output (only on
314 // success and only when the concrete socket type asked for it).
315 54x if (self->source_writer && self->res >= 0)
316 23x self->source_writer(self->source_writer_ctx,
317 23x self->source_storage, self->source_len);
318
319 54x coro_resume(self);
320 }
321 };
322
323 } // namespace boost::corosio::detail
324
325 #endif // BOOST_COROSIO_HAS_IO_URING
326
327 #endif // BOOST_COROSIO_NATIVE_DETAIL_IO_URING_IO_URING_DGRAM_OPS_HPP
328