| author | |
| committer | |
| log | d3c4158a1053e5322d137f61bf4bc643650a4741 |
| tree | e9b3e9e3b2ba91c9b74d44c0fd314461941e995c |
| parent | 35ce907c06d5758adab276927ad8dbe730d6130d |
and finish implementation of HostName.connect5 files changed, 144 insertions(+), 36 deletions(-)
lib/std/Io.zig+82-2| ... | ... | @@ -639,7 +639,7 @@ pub const VTable = struct { |
| 639 | 639 | /// Copied and then passed to `start`. |
| 640 | 640 | context: []const u8, |
| 641 | 641 | context_alignment: std.mem.Alignment, |
| 642 | start: *const fn (context: *const anyopaque) void, | |
| 642 | start: *const fn (*Group, context: *const anyopaque) void, | |
| 643 | 643 | ) void, |
| 644 | 644 | groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void, |
| 645 | 645 | groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void, |
| ... | ... | @@ -1005,7 +1005,8 @@ pub const Group = struct { |
| 1005 | 1005 | pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void { |
| 1006 | 1006 | const Args = @TypeOf(args); |
| 1007 | 1007 | const TypeErased = struct { |
| 1008 | fn start(context: *const anyopaque) void { | |
| 1008 | fn start(group: *Group, context: *const anyopaque) void { | |
| 1009 | _ = group; | |
| 1009 | 1010 | const args_casted: *const Args = @ptrCast(@alignCast(context)); |
| 1010 | 1011 | @call(.auto, function, args_casted.*); |
| 1011 | 1012 | } |
| ... | ... | @@ -1033,6 +1034,85 @@ pub const Group = struct { |
| 1033 | 1034 | } |
| 1034 | 1035 | }; |
| 1035 | 1036 | |
| 1037 | pub fn Select(comptime U: type) type { | |
| 1038 | return struct { | |
| 1039 | io: Io, | |
| 1040 | group: Group, | |
| 1041 | queue: Queue(U), | |
| 1042 | outstanding: usize, | |
| 1043 | ||
| 1044 | const S = @This(); | |
| 1045 | ||
| 1046 | pub const Union = U; | |
| 1047 | ||
| 1048 | pub const Field = std.meta.FieldEnum(U); | |
| 1049 | ||
| 1050 | pub fn init(io: Io, buffer: []U) S { | |
| 1051 | return .{ | |
| 1052 | .io = io, | |
| 1053 | .queue = .init(buffer), | |
| 1054 | .group = .init, | |
| 1055 | .outstanding = 0, | |
| 1056 | }; | |
| 1057 | } | |
| 1058 | ||
| 1059 | /// Calls `function` with `args` asynchronously. The resource spawned is | |
| 1060 | /// owned by the select. | |
| 1061 | /// | |
| 1062 | /// `function` must have return type matching the `field` field of `Union`. | |
| 1063 | /// | |
| 1064 | /// `function` *may* be called immediately, before `async` returns. | |
| 1065 | /// | |
| 1066 | /// After this is called, `wait` or `cancel` must be called before the | |
| 1067 | /// select is deinitialized. | |
| 1068 | /// | |
| 1069 | /// Threadsafe. | |
| 1070 | /// | |
| 1071 | /// Related: | |
| 1072 | /// * `Io.async` | |
| 1073 | /// * `Group.async` | |
| 1074 | pub fn async( | |
| 1075 | s: *S, | |
| 1076 | comptime field: Field, | |
| 1077 | function: anytype, | |
| 1078 | args: std.meta.ArgsTuple(@TypeOf(function)), | |
| 1079 | ) void { | |
| 1080 | const Args = @TypeOf(args); | |
| 1081 | const TypeErased = struct { | |
| 1082 | fn start(group: *Group, context: *const anyopaque) void { | |
| 1083 | const args_casted: *const Args = @ptrCast(@alignCast(context)); | |
| 1084 | const unerased_select: *S = @fieldParentPtr("group", group); | |
| 1085 | const elem = @unionInit(U, @tagName(field), @call(.auto, function, args_casted.*)); | |
| 1086 | unerased_select.queue.putOneUncancelable(unerased_select.io, elem); | |
| 1087 | } | |
| 1088 | }; | |
| 1089 | _ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic); | |
| 1090 | s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast((&args)[0..1]), .of(Args), TypeErased.start); | |
| 1091 | } | |
| 1092 | ||
| 1093 | /// Blocks until another task of the select finishes. | |
| 1094 | /// | |
| 1095 | /// Asserts there is at least one more `outstanding` task. | |
| 1096 | /// | |
| 1097 | /// Not threadsafe. | |
| 1098 | pub fn wait(s: *S) Io.Cancelable!U { | |
| 1099 | s.outstanding -= 1; | |
| 1100 | return s.queue.getOne(s.io); | |
| 1101 | } | |
| 1102 | ||
| 1103 | /// Equivalent to `wait` but requests cancellation on all remaining | |
| 1104 | /// tasks owned by the select. | |
| 1105 | /// | |
| 1106 | /// It is illegal to call `wait` after this. | |
| 1107 | /// | |
| 1108 | /// Idempotent. Not threadsafe. | |
| 1109 | pub fn cancel(s: *S) void { | |
| 1110 | s.outstanding = 0; | |
| 1111 | s.group.cancel(s.io); | |
| 1112 | } | |
| 1113 | }; | |
| 1114 | } | |
| 1115 | ||
| 1036 | 1116 | pub const Mutex = struct { |
| 1037 | 1117 | state: State, |
| 1038 | 1118 |
lib/std/Io/Threaded.zig+7-6| ... | ... | @@ -458,7 +458,7 @@ const GroupClosure = struct { |
| 458 | 458 | group: *Io.Group, |
| 459 | 459 | /// Points to sibling `GroupClosure`. Used for walking the group to cancel all. |
| 460 | 460 | node: std.SinglyLinkedList.Node, |
| 461 | func: *const fn (context: *anyopaque) void, | |
| 461 | func: *const fn (*Io.Group, context: *anyopaque) void, | |
| 462 | 462 | context_alignment: std.mem.Alignment, |
| 463 | 463 | context_len: usize, |
| 464 | 464 | |
| ... | ... | @@ -476,7 +476,7 @@ const GroupClosure = struct { |
| 476 | 476 | return; |
| 477 | 477 | } |
| 478 | 478 | current_closure = closure; |
| 479 | gc.func(gc.contextPointer()); | |
| 479 | gc.func(group, gc.contextPointer()); | |
| 480 | 480 | current_closure = null; |
| 481 | 481 | |
| 482 | 482 | // In case a cancel happens after successful task completion, prevents |
| ... | ... | @@ -512,7 +512,7 @@ fn groupAsync( |
| 512 | 512 | group: *Io.Group, |
| 513 | 513 | context: []const u8, |
| 514 | 514 | context_alignment: std.mem.Alignment, |
| 515 | start: *const fn (context: *const anyopaque) void, | |
| 515 | start: *const fn (*Io.Group, context: *const anyopaque) void, | |
| 516 | 516 | ) void { |
| 517 | 517 | if (builtin.single_threaded) return start(context.ptr); |
| 518 | 518 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| ... | ... | @@ -520,7 +520,7 @@ fn groupAsync( |
| 520 | 520 | const gpa = pool.allocator; |
| 521 | 521 | const n = GroupClosure.contextEnd(context_alignment, context.len); |
| 522 | 522 | const gc: *GroupClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(GroupClosure), n) catch { |
| 523 | return start(context.ptr); | |
| 523 | return start(group, context.ptr); | |
| 524 | 524 | })); |
| 525 | 525 | gc.* = .{ |
| 526 | 526 | .closure = .{ |
| ... | ... | @@ -548,7 +548,7 @@ fn groupAsync( |
| 548 | 548 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { |
| 549 | 549 | pool.mutex.unlock(); |
| 550 | 550 | gc.free(gpa); |
| 551 | return start(context.ptr); | |
| 551 | return start(group, context.ptr); | |
| 552 | 552 | }; |
| 553 | 553 | |
| 554 | 554 | pool.run_queue.prepend(&gc.closure.node); |
| ... | ... | @@ -558,7 +558,7 @@ fn groupAsync( |
| 558 | 558 | assert(pool.run_queue.popFirst() == &gc.closure.node); |
| 559 | 559 | pool.mutex.unlock(); |
| 560 | 560 | gc.free(gpa); |
| 561 | return start(context.ptr); | |
| 561 | return start(group, context.ptr); | |
| 562 | 562 | }; |
| 563 | 563 | pool.threads.appendAssumeCapacity(thread); |
| 564 | 564 | } |
| ... | ... | @@ -2662,6 +2662,7 @@ fn netLookupFallible( |
| 2662 | 2662 | .{ .address = addr }, |
| 2663 | 2663 | .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, |
| 2664 | 2664 | }); |
| 2665 | return; | |
| 2665 | 2666 | } else |_| {} |
| 2666 | 2667 | } |
| 2667 | 2668 |
lib/std/Io/net.zig+2-2| ... | ... | @@ -315,8 +315,8 @@ pub const IpAddress = union(enum) { |
| 315 | 315 | }; |
| 316 | 316 | |
| 317 | 317 | /// Initiates a connection-oriented network stream. |
| 318 | pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream { | |
| 319 | return io.vtable.netConnectIp(io.userdata, address, options); | |
| 318 | pub fn connect(address: IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream { | |
| 319 | return io.vtable.netConnectIp(io.userdata, &address, options); | |
| 320 | 320 | } |
| 321 | 321 | }; |
| 322 | 322 |
lib/std/Io/net/HostName.zig+8-6| ... | ... | @@ -88,7 +88,7 @@ pub const LookupResult = union(enum) { |
| 88 | 88 | /// Adds any number of `IpAddress` into resolved, exactly one canonical_name, |
| 89 | 89 | /// and then always finishes by adding one `LookupResult.end` entry. |
| 90 | 90 | /// |
| 91 | /// Guaranteed not to block if provided queue has capacity at least 8. | |
| 91 | /// Guaranteed not to block if provided queue has capacity at least 16. | |
| 92 | 92 | pub fn lookup( |
| 93 | 93 | host_name: HostName, |
| 94 | 94 | io: Io, |
| ... | ... | @@ -216,11 +216,13 @@ pub fn connect( |
| 216 | 216 | } }); |
| 217 | 217 | defer lookup_task.cancel(io); |
| 218 | 218 | |
| 219 | var select: Io.Select(union(enum) { ip_connect: IpAddress.ConnectError!Stream }) = .init; | |
| 220 | defer select.cancel(io); | |
| 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(); | |
| 221 | 223 | |
| 222 | 224 | while (results.getOne(io)) |result| switch (result) { |
| 223 | .address => |address| select.async(io, .ip_connect, IpAddress.connect, .{ address, io, options }), | |
| 225 | .address => |address| select.async(.connect_result, IpAddress.connect, .{ address, io, options }), | |
| 224 | 226 | .canonical_name => continue, |
| 225 | 227 | .end => |lookup_result| { |
| 226 | 228 | try lookup_result; |
| ... | ... | @@ -230,8 +232,8 @@ pub fn connect( |
| 230 | 232 | |
| 231 | 233 | var aggregate_error: ConnectError = error.UnknownHostName; |
| 232 | 234 | |
| 233 | while (select.remaining != 0) switch (select.wait(io)) { | |
| 234 | .ip_connect => |ip_connect| if (ip_connect) |stream| return stream else |err| switch (err) { | |
| 235 | while (select.outstanding != 0) switch (try select.wait()) { | |
| 236 | .connect_result => |connect_result| if (connect_result) |stream| return stream else |err| switch (err) { | |
| 235 | 237 | error.SystemResources => |e| return e, |
| 236 | 238 | error.OptionUnsupported => |e| return e, |
| 237 | 239 | error.ProcessFdQuotaExceeded => |e| return e, |
lib/std/Io/net/test.zig+45-20| ... | ... | @@ -1,5 +1,7 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const builtin = @import("builtin"); |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 3 | 5 | const net = std.Io.net; |
| 4 | 6 | const mem = std.mem; |
| 5 | 7 | const testing = std.testing; |
| ... | ... | @@ -126,33 +128,56 @@ test "resolve DNS" { |
| 126 | 128 | const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80); |
| 127 | 129 | const localhost_v6 = try net.IpAddress.parse("::2", 80); |
| 128 | 130 | |
| 129 | var addresses_buffer: [8]net.IpAddress = undefined; | |
| 130 | var canon_name_buffer: [net.HostName.max_len]u8 = undefined; | |
| 131 | const result = try net.HostName.lookup(try .init("localhost"), io, .{ | |
| 131 | var canonical_name_buffer: [net.HostName.max_len]u8 = undefined; | |
| 132 | var results_buffer: [32]net.HostName.LookupResult = undefined; | |
| 133 | var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer); | |
| 134 | ||
| 135 | net.HostName.lookup(try .init("localhost"), io, &results, .{ | |
| 132 | 136 | .port = 80, |
| 133 | .addresses_buffer = &addresses_buffer, | |
| 134 | .canonical_name_buffer = &canon_name_buffer, | |
| 137 | .canonical_name_buffer = &canonical_name_buffer, | |
| 135 | 138 | }); |
| 136 | for (addresses_buffer[0..result.addresses_len]) |addr| { | |
| 137 | if (addr.eql(&localhost_v4) or addr.eql(&localhost_v6)) break; | |
| 138 | } else @panic("unexpected address for localhost"); | |
| 139 | ||
| 140 | var addresses_found: usize = 0; | |
| 141 | ||
| 142 | while (results.getOne(io)) |result| switch (result) { | |
| 143 | .address => |address| { | |
| 144 | if (address.eql(&localhost_v4) or address.eql(&localhost_v6)) | |
| 145 | addresses_found += 1; | |
| 146 | }, | |
| 147 | .canonical_name => |canonical_name| try testing.expectEqualStrings("localhost", canonical_name.bytes), | |
| 148 | .end => |end| { | |
| 149 | try end; | |
| 150 | break; | |
| 151 | }, | |
| 152 | } else |err| return err; | |
| 153 | ||
| 154 | try testing.expect(addresses_found != 0); | |
| 139 | 155 | } |
| 140 | 156 | |
| 141 | 157 | { |
| 142 | 158 | // The tests are required to work even when there is no Internet connection, |
| 143 | 159 | // so some of these errors we must accept and skip the test. |
| 144 | var addresses_buffer: [8]net.IpAddress = undefined; | |
| 145 | var canon_name_buffer: [net.HostName.max_len]u8 = undefined; | |
| 146 | const result = net.HostName.lookup(try .init("example.com"), io, .{ | |
| 160 | var canonical_name_buffer: [net.HostName.max_len]u8 = undefined; | |
| 161 | var results_buffer: [16]net.HostName.LookupResult = undefined; | |
| 162 | var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer); | |
| 163 | ||
| 164 | net.HostName.lookup(try .init("example.com"), io, &results, .{ | |
| 147 | 165 | .port = 80, |
| 148 | .addresses_buffer = &addresses_buffer, | |
| 149 | .canonical_name_buffer = &canon_name_buffer, | |
| 150 | }) catch |err| switch (err) { | |
| 151 | error.UnknownHostName => return error.SkipZigTest, | |
| 152 | error.NameServerFailure => return error.SkipZigTest, | |
| 153 | else => return err, | |
| 154 | }; | |
| 155 | _ = result; | |
| 166 | .canonical_name_buffer = &canonical_name_buffer, | |
| 167 | }); | |
| 168 | ||
| 169 | while (results.getOne(io)) |result| switch (result) { | |
| 170 | .address => {}, | |
| 171 | .canonical_name => {}, | |
| 172 | .end => |end| { | |
| 173 | end catch |err| switch (err) { | |
| 174 | error.UnknownHostName => return error.SkipZigTest, | |
| 175 | error.NameServerFailure => return error.SkipZigTest, | |
| 176 | else => return err, | |
| 177 | }; | |
| 178 | break; | |
| 179 | }, | |
| 180 | } else |err| return err; | |
| 156 | 181 | } |
| 157 | 182 | } |
| 158 | 183 |