From 47f18ee6a058966bbe06e83e26a5b6ed67f368cb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Sep 2025 23:56:52 -0700 Subject: [PATCH] std.Io.net: make netSend support multiple messages this lowers to sendmmsg on linux, and means Io.Group is no longer needed, resulting in a more efficient implementation. --- lib/std/Io.zig | 2 +- lib/std/Io/Threaded.zig | 8 ++++---- lib/std/Io/net.zig | 22 +++++++++++++++++++++- lib/std/Io/net/HostName.zig | 22 +++++++++++++--------- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 6f2d05aacd..d0a5c5df8b 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -671,7 +671,7 @@ pub const VTable = struct { listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server, accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream, ipBind: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket, - netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: *const net.IpAddress, data: []const u8) net.Socket.SendError!void, + netSend: *const fn (?*anyopaque, net.Socket.Handle, []const net.OutgoingMessage, net.SendFlags) net.Socket.SendError!void, netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, buffer: []u8, timeout: Timeout) net.Socket.ReceiveTimeoutError!net.ReceivedMessage, netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize, netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 76291527f1..3e0c1380df 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1309,15 +1309,15 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n fn netSend( userdata: ?*anyopaque, handle: Io.net.Socket.Handle, - address: *const Io.net.IpAddress, - data: []const u8, + messages: []const Io.net.OutgoingMessage, + flags: Io.net.SendFlags, ) Io.net.Socket.SendError!void { const pool: *Pool = @ptrCast(@alignCast(userdata)); try pool.checkCancel(); _ = handle; - _ = address; - _ = data; + _ = messages; + _ = flags; @panic("TODO"); } diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index d06f7bbce0..02cec48b0c 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -700,6 +700,21 @@ pub const ReceivedMessage = struct { len: usize, }; +pub const OutgoingMessage = struct { + address: *const IpAddress, + data: []const u8, + control: []const u8 = &.{}, +}; + +pub const SendFlags = packed struct(u8) { + confirm: bool = false, + dont_route: bool = false, + eor: bool = false, + oob: bool = false, + fastopen: bool = false, + _: u3 = 0, +}; + pub const Interface = struct { /// Value 0 indicates `none`. index: u32, @@ -818,7 +833,12 @@ pub const Socket = struct { /// Transfers `data` to `dest`, connectionless. pub fn send(s: *const Socket, io: Io, dest: *const IpAddress, data: []const u8) SendError!void { - return io.vtable.netSend(io.userdata, s.handle, dest, data); + const message: OutgoingMessage = .{ .address = dest, .data = data }; + return io.vtable.netSend(io.userdata, s.handle, &.{message}, .{}); + } + + pub fn sendMany(s: *const Socket, io: Io, messages: []const OutgoingMessage, flags: SendFlags) SendError!void { + return io.vtable.netSend(io.userdata, s.handle, messages, flags); } pub const ReceiveError = error{} || Io.UnexpectedError || Io.Cancelable; diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig index 6f3f421a9c..e77987aa1a 100644 --- a/lib/std/Io/net/HostName.zig +++ b/lib/std/Io/net/HostName.zig @@ -271,15 +271,19 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio }; send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try io.now(.MONOTONIC)) { - var group: Io.Group = .init; - defer group.cancel(io); - + var message_buffer: [queries_buffer.len * ResolvConf.max_nameservers]Io.net.OutgoingMessage = undefined; + var message_i: usize = 0; for (queries, answers) |query, *answer| { if (answer.len != 0) continue; for (mapped_nameservers) |*ns| { - group.async(io, sendIgnoringResult, .{ io, socket.handle, ns, query }); + message_buffer[message_i] = .{ + .address = ns, + .data = query, + }; + message_i += 1; } } + io.vtable.netSend(io.userdata, socket.handle, message_buffer[0..message_i], .{}) catch {}; const timeout: Io.Timeout = .{ .deadline = now_ts.addDuration(attempt_duration) }; @@ -320,7 +324,11 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio if (next_answer_buffer == answers.len) break :send; }, 2 => { - group.async(io, sendIgnoringResult, .{ io, socket.handle, ns, query }); + const message: Io.net.OutgoingMessage = .{ + .address = ns, + .data = query, + }; + io.vtable.netSend(io.userdata, socket.handle, &.{message}, .{}) catch {}; continue; }, else => continue, @@ -382,10 +390,6 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio return error.NameServerFailure; } -fn sendIgnoringResult(io: Io, socket_handle: Io.net.Socket.Handle, dest: *const IpAddress, msg: []const u8) void { - _ = io.vtable.netSend(io.userdata, socket_handle, dest, msg) catch {}; -} - fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult { const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) { error.FileNotFound,