authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-15 10:20:04-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
logadaef433d24ce88230fe64ce20214d695cf83bc0
tree51ade56232fc8d0a75bc31d20788c45d9217d8cb
parentd3c4158a1053e5322d137f61bf4bc643650a4741

std.net.HostName.connect: rework to avoid waiting for DNS

The previous implementation would eagerly attempt TCP connection upon receiving a DNS reply, but it would still wait for all the DNS results before returning from the function. This implementation returns immediately upon first successful TCP connection, canceling not only in-flight TCP connection attempts but also unfinished DNS queries.

1 files changed, 58 insertions(+), 26 deletions(-)

lib/std/Io/net/HostName.zig+58-26
...@@ -206,34 +206,16 @@ pub fn connect(...@@ -206,34 +206,16 @@ pub fn connect(
206 port: u16,206 port: u16,
207 options: IpAddress.ConnectOptions,207 options: IpAddress.ConnectOptions,
208) ConnectError!Stream {208) ConnectError!Stream {
209 var canonical_name_buffer: [max_len]u8 = undefined;209 var connect_many_buffer: [32]ConnectManyResult = undefined;
210 var results_buffer: [32]HostName.LookupResult = undefined;210 var connect_many_queue: Io.Queue(ConnectManyResult) = .init(&connect_many_buffer);
211 var results: Io.Queue(LookupResult) = .init(&results_buffer);
212
213 var lookup_task = io.async(HostName.lookup, .{ host_name, io, &results, .{
214 .port = port,
215 .canonical_name_buffer = &canonical_name_buffer,
216 } });
217 defer lookup_task.cancel(io);
218
219 const Result = union(enum) { connect_result: IpAddress.ConnectError!Stream };
220 var finished_task_buffer: [results_buffer.len]Result = undefined;
221 var select: Io.Select(Result) = .init(io, &finished_task_buffer);
222 defer select.cancel();
223211
224 while (results.getOne(io)) |result| switch (result) {212 var connect_many = io.async(connectMany, .{ host_name, io, port, &connect_many_queue, options });
225 .address => |address| select.async(.connect_result, IpAddress.connect, .{ address, io, options }),213 defer connect_many.cancel(io);
226 .canonical_name => continue,
227 .end => |lookup_result| {
228 try lookup_result;
229 break;
230 },
231 } else |err| return err;
232214
233 var aggregate_error: ConnectError = error.UnknownHostName;215 var aggregate_error: ConnectError = error.UnknownHostName;
234216
235 while (select.outstanding != 0) switch (try select.wait()) {217 while (connect_many_queue.getOne(io)) |result| switch (result) {
236 .connect_result => |connect_result| if (connect_result) |stream| return stream else |err| switch (err) {218 .connection => |connection| if (connection) |stream| return stream else |err| switch (err) {
237 error.SystemResources => |e| return e,219 error.SystemResources => |e| return e,
238 error.OptionUnsupported => |e| return e,220 error.OptionUnsupported => |e| return e,
239 error.ProcessFdQuotaExceeded => |e| return e,221 error.ProcessFdQuotaExceeded => |e| return e,
...@@ -242,9 +224,59 @@ pub fn connect(...@@ -242,9 +224,59 @@ pub fn connect(
242 error.WouldBlock => return error.Unexpected,224 error.WouldBlock => return error.Unexpected,
243 else => |e| aggregate_error = e,225 else => |e| aggregate_error = e,
244 },226 },
245 };227 .end => |end| {
228 try end;
229 return aggregate_error;
230 },
231 } else |err| return err;
232}
233
234pub const ConnectManyResult = union(enum) {
235 connection: IpAddress.ConnectError!Stream,
236 end: ConnectError!void,
237};
238
239/// Asynchronously establishes a connection to all IP addresses associated with
240/// a host name, adding them to a results queue upon completion.
241pub fn connectMany(
242 host_name: HostName,
243 io: Io,
244 port: u16,
245 results: *Io.Queue(ConnectManyResult),
246 options: IpAddress.ConnectOptions,
247) void {
248 var canonical_name_buffer: [max_len]u8 = undefined;
249 var lookup_buffer: [32]HostName.LookupResult = undefined;
250 var lookup_queue: Io.Queue(LookupResult) = .init(&lookup_buffer);
251
252 host_name.lookup(io, &lookup_queue, .{
253 .port = port,
254 .canonical_name_buffer = &canonical_name_buffer,
255 });
256
257 var group: Io.Group = .init;
258 defer group.cancel(io);
259
260 while (lookup_queue.getOne(io)) |dns_result| switch (dns_result) {
261 .address => |address| group.async(io, enqueueConnection, .{ address, io, results, options }),
262 .canonical_name => continue,
263 .end => |lookup_result| {
264 group.wait(io);
265 results.putOneUncancelable(io, .{ .end = lookup_result });
266 return;
267 },
268 } else |err| switch (err) {
269 error.Canceled => |e| results.putOneUncancelable(io, .{ .end = e }),
270 }
271}
246272
247 return aggregate_error;273fn enqueueConnection(
274 address: IpAddress,
275 io: Io,
276 queue: *Io.Queue(ConnectManyResult),
277 options: IpAddress.ConnectOptions,
278) void {
279 queue.putOneUncancelable(io, .{ .connection = address.connect(io, options) });
248}280}
249281
250pub const ResolvConf = struct {282pub const ResolvConf = struct {