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.
This commit is contained in:
Andrew Kelley 2025-09-30 23:56:52 -07:00
parent 46e5068e48
commit 47f18ee6a0
4 changed files with 39 additions and 15 deletions

View file

@ -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,

View file

@ -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");
}

View file

@ -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;

View file

@ -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,