| 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,7 +639,7 @@ pub const VTable = struct { |
| 639 | /// Copied and then passed to `start`. | 639 | /// Copied and then passed to `start`. |
| 640 | context: []const u8, | 640 | context: []const u8, |
| 641 | context_alignment: std.mem.Alignment, | 641 | context_alignment: std.mem.Alignment, |
| 642 | start: *const fn (context: *const anyopaque) void, | 642 | start: *const fn (*Group, context: *const anyopaque) void, |
| 643 | ) void, | 643 | ) void, |
| 644 | groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void, | 644 | groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void, |
| 645 | groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void, | 645 | groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void, |
| ... | @@ -1005,7 +1005,8 @@ pub const Group = struct { | ... | @@ -1005,7 +1005,8 @@ pub const Group = struct { |
| 1005 | pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void { | 1005 | pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void { |
| 1006 | const Args = @TypeOf(args); | 1006 | const Args = @TypeOf(args); |
| 1007 | const TypeErased = struct { | 1007 | const TypeErased = struct { |
| 1008 | fn start(context: *const anyopaque) void { | 1008 | fn start(group: *Group, context: *const anyopaque) void { |
| 1009 | _ = group; | ||
| 1009 | const args_casted: *const Args = @ptrCast(@alignCast(context)); | 1010 | const args_casted: *const Args = @ptrCast(@alignCast(context)); |
| 1010 | @call(.auto, function, args_casted.*); | 1011 | @call(.auto, function, args_casted.*); |
| 1011 | } | 1012 | } |
| ... | @@ -1033,6 +1034,85 @@ pub const Group = struct { | ... | @@ -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 | pub const Mutex = struct { | 1116 | pub const Mutex = struct { |
| 1037 | state: State, | 1117 | state: State, |
| 1038 | 1118 |
lib/std/Io/Threaded.zig+7-6| ... | @@ -458,7 +458,7 @@ const GroupClosure = struct { | ... | @@ -458,7 +458,7 @@ const GroupClosure = struct { |
| 458 | group: *Io.Group, | 458 | group: *Io.Group, |
| 459 | /// Points to sibling `GroupClosure`. Used for walking the group to cancel all. | 459 | /// Points to sibling `GroupClosure`. Used for walking the group to cancel all. |
| 460 | node: std.SinglyLinkedList.Node, | 460 | node: std.SinglyLinkedList.Node, |
| 461 | func: *const fn (context: *anyopaque) void, | 461 | func: *const fn (*Io.Group, context: *anyopaque) void, |
| 462 | context_alignment: std.mem.Alignment, | 462 | context_alignment: std.mem.Alignment, |
| 463 | context_len: usize, | 463 | context_len: usize, |
| 464 | 464 | ||
| ... | @@ -476,7 +476,7 @@ const GroupClosure = struct { | ... | @@ -476,7 +476,7 @@ const GroupClosure = struct { |
| 476 | return; | 476 | return; |
| 477 | } | 477 | } |
| 478 | current_closure = closure; | 478 | current_closure = closure; |
| 479 | gc.func(gc.contextPointer()); | 479 | gc.func(group, gc.contextPointer()); |
| 480 | current_closure = null; | 480 | current_closure = null; |
| 481 | 481 | ||
| 482 | // In case a cancel happens after successful task completion, prevents | 482 | // In case a cancel happens after successful task completion, prevents |
| ... | @@ -512,7 +512,7 @@ fn groupAsync( | ... | @@ -512,7 +512,7 @@ fn groupAsync( |
| 512 | group: *Io.Group, | 512 | group: *Io.Group, |
| 513 | context: []const u8, | 513 | context: []const u8, |
| 514 | context_alignment: std.mem.Alignment, | 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 | ) void { | 516 | ) void { |
| 517 | if (builtin.single_threaded) return start(context.ptr); | 517 | if (builtin.single_threaded) return start(context.ptr); |
| 518 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 518 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| ... | @@ -520,7 +520,7 @@ fn groupAsync( | ... | @@ -520,7 +520,7 @@ fn groupAsync( |
| 520 | const gpa = pool.allocator; | 520 | const gpa = pool.allocator; |
| 521 | const n = GroupClosure.contextEnd(context_alignment, context.len); | 521 | const n = GroupClosure.contextEnd(context_alignment, context.len); |
| 522 | const gc: *GroupClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(GroupClosure), n) catch { | 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 | gc.* = .{ | 525 | gc.* = .{ |
| 526 | .closure = .{ | 526 | .closure = .{ |
| ... | @@ -548,7 +548,7 @@ fn groupAsync( | ... | @@ -548,7 +548,7 @@ fn groupAsync( |
| 548 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { | 548 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { |
| 549 | pool.mutex.unlock(); | 549 | pool.mutex.unlock(); |
| 550 | gc.free(gpa); | 550 | gc.free(gpa); |
| 551 | return start(context.ptr); | 551 | return start(group, context.ptr); |
| 552 | }; | 552 | }; |
| 553 | 553 | ||
| 554 | pool.run_queue.prepend(&gc.closure.node); | 554 | pool.run_queue.prepend(&gc.closure.node); |
| ... | @@ -558,7 +558,7 @@ fn groupAsync( | ... | @@ -558,7 +558,7 @@ fn groupAsync( |
| 558 | assert(pool.run_queue.popFirst() == &gc.closure.node); | 558 | assert(pool.run_queue.popFirst() == &gc.closure.node); |
| 559 | pool.mutex.unlock(); | 559 | pool.mutex.unlock(); |
| 560 | gc.free(gpa); | 560 | gc.free(gpa); |
| 561 | return start(context.ptr); | 561 | return start(group, context.ptr); |
| 562 | }; | 562 | }; |
| 563 | pool.threads.appendAssumeCapacity(thread); | 563 | pool.threads.appendAssumeCapacity(thread); |
| 564 | } | 564 | } |
| ... | @@ -2662,6 +2662,7 @@ fn netLookupFallible( | ... | @@ -2662,6 +2662,7 @@ fn netLookupFallible( |
| 2662 | .{ .address = addr }, | 2662 | .{ .address = addr }, |
| 2663 | .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, | 2663 | .{ .canonical_name = copyCanon(options.canonical_name_buffer, name) }, |
| 2664 | }); | 2664 | }); |
| 2665 | return; | ||
| 2665 | } else |_| {} | 2666 | } else |_| {} |
| 2666 | } | 2667 | } |
| 2667 | 2668 |
lib/std/Io/net.zig+2-2| ... | @@ -315,8 +315,8 @@ pub const IpAddress = union(enum) { | ... | @@ -315,8 +315,8 @@ pub const IpAddress = union(enum) { |
| 315 | }; | 315 | }; |
| 316 | 316 | ||
| 317 | /// Initiates a connection-oriented network stream. | 317 | /// Initiates a connection-oriented network stream. |
| 318 | pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream { | 318 | pub fn connect(address: IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream { |
| 319 | return io.vtable.netConnectIp(io.userdata, address, options); | 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,7 +88,7 @@ pub const LookupResult = union(enum) { |
| 88 | /// Adds any number of `IpAddress` into resolved, exactly one canonical_name, | 88 | /// Adds any number of `IpAddress` into resolved, exactly one canonical_name, |
| 89 | /// and then always finishes by adding one `LookupResult.end` entry. | 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 | pub fn lookup( | 92 | pub fn lookup( |
| 93 | host_name: HostName, | 93 | host_name: HostName, |
| 94 | io: Io, | 94 | io: Io, |
| ... | @@ -216,11 +216,13 @@ pub fn connect( | ... | @@ -216,11 +216,13 @@ pub fn connect( |
| 216 | } }); | 216 | } }); |
| 217 | defer lookup_task.cancel(io); | 217 | defer lookup_task.cancel(io); |
| 218 | 218 | ||
| 219 | var select: Io.Select(union(enum) { ip_connect: IpAddress.ConnectError!Stream }) = .init; | 219 | const Result = union(enum) { connect_result: IpAddress.ConnectError!Stream }; |
| 220 | defer select.cancel(io); | 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 | while (results.getOne(io)) |result| switch (result) { | 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 | .canonical_name => continue, | 226 | .canonical_name => continue, |
| 225 | .end => |lookup_result| { | 227 | .end => |lookup_result| { |
| 226 | try lookup_result; | 228 | try lookup_result; |
| ... | @@ -230,8 +232,8 @@ pub fn connect( | ... | @@ -230,8 +232,8 @@ pub fn connect( |
| 230 | 232 | ||
| 231 | var aggregate_error: ConnectError = error.UnknownHostName; | 233 | var aggregate_error: ConnectError = error.UnknownHostName; |
| 232 | 234 | ||
| 233 | while (select.remaining != 0) switch (select.wait(io)) { | 235 | while (select.outstanding != 0) switch (try select.wait()) { |
| 234 | .ip_connect => |ip_connect| if (ip_connect) |stream| return stream else |err| switch (err) { | 236 | .connect_result => |connect_result| if (connect_result) |stream| return stream else |err| switch (err) { |
| 235 | error.SystemResources => |e| return e, | 237 | error.SystemResources => |e| return e, |
| 236 | error.OptionUnsupported => |e| return e, | 238 | error.OptionUnsupported => |e| return e, |
| 237 | error.ProcessFdQuotaExceeded => |e| return e, | 239 | error.ProcessFdQuotaExceeded => |e| return e, |
lib/std/Io/net/test.zig+45-20| ... | @@ -1,5 +1,7 @@ | ... | @@ -1,5 +1,7 @@ |
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | 1 | const builtin = @import("builtin"); |
| 2 | |||
| 3 | const std = @import("std"); | ||
| 4 | const Io = std.Io; | ||
| 3 | const net = std.Io.net; | 5 | const net = std.Io.net; |
| 4 | const mem = std.mem; | 6 | const mem = std.mem; |
| 5 | const testing = std.testing; | 7 | const testing = std.testing; |
| ... | @@ -126,33 +128,56 @@ test "resolve DNS" { | ... | @@ -126,33 +128,56 @@ test "resolve DNS" { |
| 126 | const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80); | 128 | const localhost_v4 = try net.IpAddress.parse("127.0.0.1", 80); |
| 127 | const localhost_v6 = try net.IpAddress.parse("::2", 80); | 129 | const localhost_v6 = try net.IpAddress.parse("::2", 80); |
| 128 | 130 | ||
| 129 | var addresses_buffer: [8]net.IpAddress = undefined; | 131 | var canonical_name_buffer: [net.HostName.max_len]u8 = undefined; |
| 130 | var canon_name_buffer: [net.HostName.max_len]u8 = undefined; | 132 | var results_buffer: [32]net.HostName.LookupResult = undefined; |
| 131 | const result = try net.HostName.lookup(try .init("localhost"), io, .{ | 133 | var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer); |
| 134 | |||
| 135 | net.HostName.lookup(try .init("localhost"), io, &results, .{ | ||
| 132 | .port = 80, | 136 | .port = 80, |
| 133 | .addresses_buffer = &addresses_buffer, | 137 | .canonical_name_buffer = &canonical_name_buffer, |
| 134 | .canonical_name_buffer = &canon_name_buffer, | ||
| 135 | }); | 138 | }); |
| 136 | for (addresses_buffer[0..result.addresses_len]) |addr| { | 139 | |
| 137 | if (addr.eql(&localhost_v4) or addr.eql(&localhost_v6)) break; | 140 | var addresses_found: usize = 0; |
| 138 | } else @panic("unexpected address for localhost"); | 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 | // The tests are required to work even when there is no Internet connection, | 158 | // The tests are required to work even when there is no Internet connection, |
| 143 | // so some of these errors we must accept and skip the test. | 159 | // so some of these errors we must accept and skip the test. |
| 144 | var addresses_buffer: [8]net.IpAddress = undefined; | 160 | var canonical_name_buffer: [net.HostName.max_len]u8 = undefined; |
| 145 | var canon_name_buffer: [net.HostName.max_len]u8 = undefined; | 161 | var results_buffer: [16]net.HostName.LookupResult = undefined; |
| 146 | const result = net.HostName.lookup(try .init("example.com"), io, .{ | 162 | var results: Io.Queue(net.HostName.LookupResult) = .init(&results_buffer); |
| 163 | |||
| 164 | net.HostName.lookup(try .init("example.com"), io, &results, .{ | ||
| 147 | .port = 80, | 165 | .port = 80, |
| 148 | .addresses_buffer = &addresses_buffer, | 166 | .canonical_name_buffer = &canonical_name_buffer, |
| 149 | .canonical_name_buffer = &canon_name_buffer, | 167 | }); |
| 150 | }) catch |err| switch (err) { | 168 | |
| 151 | error.UnknownHostName => return error.SkipZigTest, | 169 | while (results.getOne(io)) |result| switch (result) { |
| 152 | error.NameServerFailure => return error.SkipZigTest, | 170 | .address => {}, |
| 153 | else => return err, | 171 | .canonical_name => {}, |
| 154 | }; | 172 | .end => |end| { |
| 155 | _ = result; | 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 |