authorgravatar for hemisputnik@proton.mehemisputnik <hemisputnik@proton.me> 2026-07-22 09:06:26+03:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-10-01 10:55:12+02:00
log20355494085a6eb59b73816038a5d0c56b5492c6
tree07234e26640c2f1626046fb5853d199e8af45a9a
parent8909d25b365de94d4b37c613ce3276634765a86b
signature Commit is signed but in an unrecognized format.

std.Io.net: improve reception of control data

A couple of changes to improve reception of control data when using the datagram API. Instead of having a separate control buffer per received message, there's one big buffer that all messages share (like the data buffer). This means that now IncomingMessage has no user-initialised fields, and can be initialised with undefined (which means that .init is now deprecated). Also, instead of taking a pointer to mutable IncomingMessage, netReceiveOne* functions simply return a IncomingMessage.

5 files changed, 106 insertions(+), 23 deletions(-)

lib/std/Io.zig+1
......@@ -346,6 +346,7 @@ pub const Operation = union(enum) {
346346 socket_handle: net.Socket.Handle,
347347 message_buffer: []net.IncomingMessage,
348348 data_buffer: []u8,
349 control_buffer: []u8 = &.{},
349350 flags: net.ReceiveFlags,
350351
351352 pub const Error = error{
lib/std/Io/Kqueue.zig+2
......@@ -1183,6 +1183,7 @@ fn netReceive(
11831183 handle: net.Socket.Handle,
11841184 message_buffer: []net.IncomingMessage,
11851185 data_buffer: []u8,
1186 control_buffer: []u8,
11861187 flags: net.ReceiveFlags,
11871188 timeout: Io.Timeout,
11881189) struct { ?net.Socket.ReceiveTimeoutError, usize } {
......@@ -1191,6 +1192,7 @@ fn netReceive(
11911192 _ = handle;
11921193 _ = message_buffer;
11931194 _ = data_buffer;
1195 _ = control_buffer;
11941196 _ = flags;
11951197 _ = timeout;
11961198 @panic("TODO");
lib/std/Io/Threaded.zig+18-8
......@@ -2555,8 +2555,8 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper
25552555 .device_io_control => |*o| return .{ .device_io_control = try deviceIoControl(o) },
25562556 .net_receive => |*o| return .{ .net_receive = o: {
25572557 if (!have_networking) break :o .{ error.NetworkDown, 0 };
2558 if (is_windows) break :o netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags);
2559 netReceivePosix(o.socket_handle, &o.message_buffer[0], o.data_buffer, o.flags, false) catch |err| switch (err) {
2558 if (is_windows) break :o netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.control_buffer, o.flags);
2559 netReceiveOnePosix(o.socket_handle, &o.message_buffer[0], o.data_buffer, o.control_buffer, o.flags, false) catch |err| switch (err) {
25602560 error.Canceled => |e| return e,
25612561 error.WouldBlock => unreachable,
25622562 else => |e| break :o .{ e, 0 },
......@@ -2825,9 +2825,11 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
28252825 .device_io_control => |o| try poll_storage.add(o.file.handle, posix.POLL.IN | posix.POLL.OUT | posix.POLL.ERR),
28262826 .net_receive => |*o| nb: {
28272827 var data_i: usize = 0;
2828 var control_i: usize = 0;
28282829 const result: Io.Operation.Result = .{ .net_receive = for (o.message_buffer, 0..) |*msg, msg_i| {
28292830 const remaining_data_buffer = o.data_buffer[data_i..];
2830 netReceivePosix(o.socket_handle, msg, remaining_data_buffer, o.flags, true) catch |err| switch (err) {
2831 const remaining_control_buffer = o.control_buffer[control_i..];
2832 netReceiveOnePosix(o.socket_handle, msg, remaining_data_buffer, remaining_control_buffer, o.flags, true) catch |err| switch (err) {
28312833 error.Canceled => |e| return e,
28322834 error.WouldBlock => {
28332835 if (msg_i != 0) break .{ null, msg_i };
......@@ -2837,6 +2839,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
28372839 else => |e| break .{ e, 0 },
28382840 };
28392841 data_i += msg.data.len;
2842 control_i += msg.control.len;
28402843 } else .{ null, o.message_buffer.len } };
28412844 switch (b.completed.tail) {
28422845 .none => b.completed.head = index,
......@@ -3279,7 +3282,7 @@ fn batchDrainSubmittedWindows(t: *Threaded, b: *Io.Batch, concurrency: bool) (Io
32793282 // TODO integrate with overlapped I/O or equivalent to avoid this error
32803283 if (concurrency) return error.ConcurrencyUnavailable;
32813284 batchCompleteBlockingWindows(b, operation_userdata, .{
3282 .net_receive = netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.flags),
3285 .net_receive = netReceiveWindows(t, o.socket_handle, o.message_buffer, o.data_buffer, o.control_buffer, o.flags),
32833286 });
32843287 },
32853288 .net_send => |*o| {
......@@ -13212,10 +13215,11 @@ fn netSendManyPosix(
1321213215 }
1321313216}
1321413217
13215fn netReceivePosix(
13218fn netReceiveOnePosix(
1321613219 socket_handle: net.Socket.Handle,
1321713220 message: *net.IncomingMessage,
1321813221 data_buffer: []u8,
13222 control_buffer: []u8,
1321913223 flags: net.ReceiveFlags,
1322013224 nonblocking: bool,
1322113225) (net.Socket.ReceiveError || error{WouldBlock})!void {
......@@ -13238,22 +13242,24 @@ fn netReceivePosix(
1323813242 .namelen = @sizeOf(PosixAddress),
1323913243 .iov = (&iov)[0..1],
1324013244 .iovlen = 1,
13241 .control = message.control.ptr,
13242 .controllen = @intCast(message.control.len),
13245 .control = control_buffer.ptr,
13246 .controllen = @intCast(control_buffer.len),
1324313247 .flags = 0,
1324413248 };
1324513249
1324613250 const syscall = try Syscall.start();
1324713251 while (true) {
1324813252 const rc = posix.system.recvmsg(socket_handle, &msg, posix_flags);
13253 const rc_control = msg.controllen;
1324913254 switch (posix.errno(rc)) {
1325013255 .SUCCESS => {
1325113256 syscall.finish();
1325213257 const data = data_buffer[0..@intCast(rc)];
13258 const control = control_buffer[0..@intCast(rc_control)];
1325313259 message.* = .{
1325413260 .from = addressFromPosix(&storage),
1325513261 .data = data,
13256 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control,
13262 .control = control,
1325713263 .flags = .{
1325813264 .eor = (msg.flags & posix.MSG.EOR) != 0,
1325913265 .trunc = (msg.flags & posix.MSG.TRUNC) != 0,
......@@ -13294,8 +13300,12 @@ fn netReceiveWindows(
1329413300 socket_handle: net.Socket.Handle,
1329513301 message_buffer: []net.IncomingMessage,
1329613302 data_buffer: []u8,
13303 control_buffer: []u8,
1329713304 flags: net.ReceiveFlags,
1329813305) struct { ?net.Socket.ReceiveError, usize } {
13306 // Windows doesn't have the concept of control/ancillary data.
13307 _ = control_buffer;
13308
1329913309 t.netReceiveOneWindows(socket_handle, &message_buffer[0], data_buffer, flags) catch |err|
1330013310 return .{ err, 0 };
1330113311 return .{ null, 1 };
lib/std/Io/Uring.zig+9-4
......@@ -2095,7 +2095,7 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper
20952095 },
20962096 .net_receive => |o| .{
20972097 .net_receive = r: {
2098 const opt_err, const n = ev.netReceive(&maybe_sync.cancel_region, o.socket_handle, o.message_buffer, o.data_buffer, o.flags);
2098 const opt_err, const n = ev.netReceive(&maybe_sync.cancel_region, o.socket_handle, o.message_buffer, o.data_buffer, o.control_buffer, o.flags);
20992099 break :r .{
21002100 if (opt_err) |err| switch (err) {
21012101 error.Canceled => |e| return e,
......@@ -5075,15 +5075,18 @@ fn netReceive(
50755075 handle: net.Socket.Handle,
50765076 message_buffer: []net.IncomingMessage,
50775077 data_buffer: []u8,
5078 control_buffer: []u8,
50785079 flags: net.ReceiveFlags,
50795080) struct { ?net.Socket.ReceiveError, usize } {
50805081 var message_i: usize = 0;
50815082 var data_i: usize = 0;
5083 var control_i: usize = 0;
50825084
50835085 while (true) {
50845086 if (message_buffer.len - message_i == 0) return .{ null, message_i };
50855087 const message = &message_buffer[message_i];
50865088 const remaining_data_buffer = data_buffer[data_i..];
5089 const remaining_control_buffer = control_buffer[control_i..];
50875090 var storage: PosixAddress = undefined;
50885091 var iov: iovec = .{ .base = remaining_data_buffer.ptr, .len = remaining_data_buffer.len };
50895092 var msg: linux.msghdr = .{
......@@ -5091,8 +5094,8 @@ fn netReceive(
50915094 .namelen = @sizeOf(PosixAddress),
50925095 .iov = (&iov)[0..1],
50935096 .iovlen = 1,
5094 .control = message.control.ptr,
5095 .controllen = @intCast(message.control.len),
5097 .control = remaining_control_buffer.ptr,
5098 .controllen = @intCast(remaining_control_buffer.len),
50965099 .flags = undefined,
50975100 };
50985101
......@@ -5121,11 +5124,13 @@ fn netReceive(
51215124 switch (completion.errno()) {
51225125 .SUCCESS => {
51235126 const data = remaining_data_buffer[0..@intCast(completion.result)];
5127 const control = remaining_control_buffer[0..@intCast(msg.controllen)];
51245128 data_i += data.len;
5129 control_i += @intCast(msg.controllen);
51255130 message.* = .{
51265131 .from = addressFromPosix(&storage),
51275132 .data = data,
5128 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control,
5133 .control = control,
51295134 .flags = .{
51305135 .eor = msg.flags & linux.MSG.EOR != 0,
51315136 .trunc = msg.flags & linux.MSG.TRUNC != 0,
lib/std/Io/net.zig+76-11
......@@ -924,22 +924,20 @@ pub const ReceiveFlags = packed struct(u8) {
924924 _: u5 = 0,
925925};
926926
927/// Fields are populated by receive functions.
927928pub const IncomingMessage = struct {
928 /// Populated by receive functions.
929929 from: IpAddress,
930 /// Populated by receive functions, points into the caller-supplied buffer.
930 /// Points into caller-supplied buffer.
931931 data: []u8,
932 /// Supplied by caller before calling receive functions; mutated by receive
933 /// functions.
932 /// Points into caller-supplied buffer.
934933 control: []u8,
935 /// Populated by receive functions.
936934 flags: Flags,
937935
938 /// Useful for initializing before calling `receiveManyTimeout`.
936 /// Deprecated. Initialize with `undefined`.
939937 pub const init: IncomingMessage = .{
940938 .from = undefined,
941939 .data = undefined,
942 .control = &.{},
940 .control = undefined,
943941 .flags = undefined,
944942 };
945943
......@@ -1162,13 +1160,30 @@ pub const Socket = struct {
11621160 /// Waits for data. Connectionless.
11631161 ///
11641162 /// See also:
1163 /// * `receiveWithControl`
11651164 /// * `receiveTimeout`
1165 /// * `receiveWithControlTimeout`
1166 /// * `receiveManyTimeout`
1167 /// * `receiveManyWithControlTimeout`
11661168 pub fn receive(s: *const Socket, io: Io, buffer: []u8) ReceiveError!IncomingMessage {
1167 var message: IncomingMessage = .init;
1169 return s.receiveWithControl(io, buffer, &.{});
1170 }
1171
1172 /// Waits for data, allowing the reception of control data. Connectionless.
1173 ///
1174 /// See also:
1175 /// * `receive`
1176 /// * `receiveTimeout`
1177 /// * `receiveWithControlTimeout`
1178 /// * `receiveManyTimeout`
1179 /// * `receiveManyWithControlTimeout`
1180 pub fn receiveWithControl(s: *const Socket, io: Io, buffer: []u8, control: []u8) ReceiveError!IncomingMessage {
1181 var message: IncomingMessage = undefined;
11681182 const maybe_err, const count = (try io.operate(.{ .net_receive = .{
11691183 .socket_handle = s.handle,
11701184 .message_buffer = (&message)[0..1],
11711185 .data_buffer = buffer,
1186 .control_buffer = control,
11721187 .flags = .{},
11731188 } })).net_receive;
11741189 if (maybe_err) |err| return err;
......@@ -1184,18 +1199,42 @@ pub const Socket = struct {
11841199 ///
11851200 /// See also:
11861201 /// * `receive`
1202 /// * `receiveWithControl`
1203 /// * `receiveWithControlTimeout`
11871204 /// * `receiveManyTimeout`
1205 /// * `receiveManyWithControlTimeout`
11881206 pub fn receiveTimeout(
11891207 s: *const Socket,
11901208 io: Io,
11911209 buffer: []u8,
11921210 timeout: Io.Timeout,
11931211 ) ReceiveTimeoutError!IncomingMessage {
1194 var message: IncomingMessage = .init;
1212 return s.receiveWithControlTimeout(io, buffer, &.{}, timeout);
1213 }
1214
1215 /// Waits for data, allowing the reception of control data. Connectionless.
1216 ///
1217 /// Returns `error.Timeout` if no message arrives early enough.
1218 ///
1219 /// See also:
1220 /// * `receive`
1221 /// * `receiveWithControl`
1222 /// * `receiveTimeout`
1223 /// * `receiveManyTimeout`
1224 /// * `receiveManyWithControlTimeout`
1225 pub fn receiveWithControlTimeout(
1226 s: *const Socket,
1227 io: Io,
1228 buffer: []u8,
1229 control: []u8,
1230 timeout: Io.Timeout,
1231 ) ReceiveTimeoutError!IncomingMessage {
1232 var message: IncomingMessage = undefined;
11951233 const maybe_err, const count = (try io.operateTimeout(.{ .net_receive = .{
11961234 .socket_handle = s.handle,
11971235 .message_buffer = (&message)[0..1],
11981236 .data_buffer = buffer,
1237 .control_buffer = control,
11991238 .flags = .{},
12001239 } }, timeout)).net_receive;
12011240 if (maybe_err) |err| return err;
......@@ -1211,21 +1250,47 @@ pub const Socket = struct {
12111250 ///
12121251 /// See also:
12131252 /// * `receive`
1253 /// * `receiveWithControl`
12141254 /// * `receiveTimeout`
1255 /// * `receiveWithControlTimeout`
1256 /// * `receiveManyWithControlTimeout`
12151257 pub fn receiveManyTimeout(
12161258 s: *const Socket,
12171259 io: Io,
1218 /// Function assumes each element has initialized `control` field.
1219 /// Initializing with `IncomingMessage.init` may be helpful.
12201260 message_buffer: []IncomingMessage,
12211261 data_buffer: []u8,
12221262 flags: ReceiveFlags,
12231263 timeout: Io.Timeout,
1264 ) struct { ?ReceiveTimeoutError, usize } {
1265 return s.receiveManyWithControlTimeout(io, message_buffer, data_buffer, &.{}, flags, timeout);
1266 }
1267
1268 /// Waits until at least one message is delivered, possibly returning more
1269 /// than one message. Allows reception of control data. Connectionless.
1270 ///
1271 /// Returns number of messages received, or `error.Timeout` if no message
1272 /// arrives early enough.
1273 ///
1274 /// See also:
1275 /// * `receive`
1276 /// * `receiveWithControl`
1277 /// * `receiveTimeout`
1278 /// * `receiveWithControlTimeout`
1279 /// * `receiveManyTimeout`
1280 pub fn receiveManyWithControlTimeout(
1281 s: *const Socket,
1282 io: Io,
1283 message_buffer: []IncomingMessage,
1284 data_buffer: []u8,
1285 control_buffer: []u8,
1286 flags: ReceiveFlags,
1287 timeout: Io.Timeout,
12241288 ) struct { ?ReceiveTimeoutError, usize } {
12251289 const result = io.operateTimeout(.{ .net_receive = .{
12261290 .socket_handle = s.handle,
12271291 .message_buffer = message_buffer,
12281292 .data_buffer = data_buffer,
1293 .control_buffer = control_buffer,
12291294 .flags = flags,
12301295 } }, timeout) catch |err| return .{ err, 0 };
12311296 return result.net_receive;