Commit cde5a51d0c

Andrew Kelley <andrew@ziglang.org>
2025-10-01 08:56:52
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.
1 parent b224002
Changed files (4)
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,
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;
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");
 }
 
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,