| author | |
| committer | |
| log | 46e5068e48fdc7ecdc6121b978c87fad680cbe40 |
| tree | 672e811bd1a3c6dd2ecb5c678d0bcf2702999dc8 |
| parent | a37c0bca2248e5d2e18c4855ee8d8d17bf26aa26 |
4 files changed, 237 insertions(+), 148 deletions(-)
lib/std/Io.zig+34-18| ... | ... | @@ -641,8 +641,8 @@ pub const VTable = struct { |
| 641 | 641 | context_alignment: std.mem.Alignment, |
| 642 | 642 | start: *const fn (context: *const anyopaque) void, |
| 643 | 643 | ) void, |
| 644 | groupWait: *const fn (?*anyopaque, *Group) void, | |
| 645 | groupCancel: *const fn (?*anyopaque, *Group) void, | |
| 644 | groupWait: *const fn (?*anyopaque, *Group, token: *anyopaque) void, | |
| 645 | groupCancel: *const fn (?*anyopaque, *Group, token: *anyopaque) void, | |
| 646 | 646 | |
| 647 | 647 | /// Blocks until one of the futures from the list has a result ready, such |
| 648 | 648 | /// that awaiting it will not block. Returns that index. |
| ... | ... | @@ -665,14 +665,14 @@ pub const VTable = struct { |
| 665 | 665 | fileSeekBy: *const fn (?*anyopaque, file: File, offset: i64) File.SeekError!void, |
| 666 | 666 | fileSeekTo: *const fn (?*anyopaque, file: File, offset: u64) File.SeekError!void, |
| 667 | 667 | |
| 668 | now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp, | |
| 669 | sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void, | |
| 668 | now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) NowError!Timestamp, | |
| 669 | sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, timeout: Timeout) SleepError!void, | |
| 670 | 670 | |
| 671 | 671 | listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server, |
| 672 | 672 | accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream, |
| 673 | 673 | ipBind: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket, |
| 674 | netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, data: []const u8) net.Socket.SendError!void, | |
| 675 | netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, buffer: []u8) net.Socket.ReceiveError!void, | |
| 674 | netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: *const net.IpAddress, data: []const u8) net.Socket.SendError!void, | |
| 675 | netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, buffer: []u8, timeout: Timeout) net.Socket.ReceiveTimeoutError!net.ReceivedMessage, | |
| 676 | 676 | netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize, |
| 677 | 677 | netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, |
| 678 | 678 | netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, |
| ... | ... | @@ -710,6 +710,15 @@ pub const Timestamp = enum(i96) { |
| 710 | 710 | pub fn addDuration(from: Timestamp, duration: Duration) Timestamp { |
| 711 | 711 | return @enumFromInt(@intFromEnum(from) + duration.nanoseconds); |
| 712 | 712 | } |
| 713 | ||
| 714 | pub fn fromNow(io: Io, clockid: std.posix.clockid_t, duration: Duration) NowError!Timestamp { | |
| 715 | const now_ts = try now(io, clockid); | |
| 716 | return addDuration(now_ts, duration); | |
| 717 | } | |
| 718 | ||
| 719 | pub fn compare(lhs: Timestamp, op: std.math.CompareOperator, rhs: Timestamp) bool { | |
| 720 | return std.math.compare(@intFromEnum(lhs), op, @intFromEnum(rhs)); | |
| 721 | } | |
| 713 | 722 | }; |
| 714 | 723 | pub const Duration = struct { |
| 715 | 724 | nanoseconds: i96, |
| ... | ... | @@ -722,11 +731,14 @@ pub const Duration = struct { |
| 722 | 731 | return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s }; |
| 723 | 732 | } |
| 724 | 733 | }; |
| 725 | pub const Deadline = union(enum) { | |
| 734 | pub const Timeout = union(enum) { | |
| 735 | none, | |
| 726 | 736 | duration: Duration, |
| 727 | timestamp: Timestamp, | |
| 737 | deadline: Timestamp, | |
| 738 | ||
| 739 | pub const Error = error{Timeout}; | |
| 728 | 740 | }; |
| 729 | pub const ClockGetTimeError = std.posix.ClockGetTimeError || Cancelable; | |
| 741 | pub const NowError = std.posix.ClockGetTimeError || Cancelable; | |
| 730 | 742 | pub const SleepError = error{ UnsupportedClock, Unexpected, Canceled }; |
| 731 | 743 | |
| 732 | 744 | pub const AnyFuture = opaque {}; |
| ... | ... | @@ -768,8 +780,10 @@ pub const Group = struct { |
| 768 | 780 | /// |
| 769 | 781 | /// `function` *may* be called immediately, before `async` returns. |
| 770 | 782 | /// |
| 771 | /// After this is called, `wait` must be called before the group is | |
| 772 | /// deinitialized. | |
| 783 | /// After this is called, `wait` or `cancel` must be called before the | |
| 784 | /// group is deinitialized. | |
| 785 | /// | |
| 786 | /// Threadsafe. | |
| 773 | 787 | /// |
| 774 | 788 | /// See also: |
| 775 | 789 | /// * `Io.async` |
| ... | ... | @@ -789,7 +803,9 @@ pub const Group = struct { |
| 789 | 803 | /// |
| 790 | 804 | /// Idempotent. Not threadsafe. |
| 791 | 805 | pub fn wait(g: *Group, io: Io) void { |
| 792 | io.vtable.groupWait(io.userdata, g); | |
| 806 | const token = g.token orelse return; | |
| 807 | g.token = null; | |
| 808 | io.vtable.groupWait(io.userdata, g, token); | |
| 793 | 809 | } |
| 794 | 810 | |
| 795 | 811 | /// Equivalent to `wait` but requests cancellation on all tasks owned by |
| ... | ... | @@ -797,9 +813,9 @@ pub const Group = struct { |
| 797 | 813 | /// |
| 798 | 814 | /// Idempotent. Not threadsafe. |
| 799 | 815 | pub fn cancel(g: *Group, io: Io) void { |
| 800 | if (g.token == null) return; | |
| 801 | io.vtable.groupCancel(io.userdata, g); | |
| 802 | assert(g.token == null); | |
| 816 | const token = g.token orelse return; | |
| 817 | g.token = null; | |
| 818 | io.vtable.groupCancel(io.userdata, g, token); | |
| 803 | 819 | } |
| 804 | 820 | }; |
| 805 | 821 | |
| ... | ... | @@ -1215,12 +1231,12 @@ pub fn cancelRequested(io: Io) bool { |
| 1215 | 1231 | return io.vtable.cancelRequested(io.userdata); |
| 1216 | 1232 | } |
| 1217 | 1233 | |
| 1218 | pub fn now(io: Io, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp { | |
| 1234 | pub fn now(io: Io, clockid: std.posix.clockid_t) NowError!Timestamp { | |
| 1219 | 1235 | return io.vtable.now(io.userdata, clockid); |
| 1220 | 1236 | } |
| 1221 | 1237 | |
| 1222 | pub fn sleep(io: Io, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void { | |
| 1223 | return io.vtable.sleep(io.userdata, clockid, deadline); | |
| 1238 | pub fn sleep(io: Io, clockid: std.posix.clockid_t, timeout: Timeout) SleepError!void { | |
| 1239 | return io.vtable.sleep(io.userdata, clockid, timeout); | |
| 1224 | 1240 | } |
| 1225 | 1241 | |
| 1226 | 1242 | pub fn sleepDuration(io: Io, duration: Duration) SleepError!void { |
lib/std/Io/Threaded.zig+58-27| ... | ... | @@ -463,16 +463,19 @@ fn groupAsync( |
| 463 | 463 | }, |
| 464 | 464 | .pool = pool, |
| 465 | 465 | .group = group, |
| 466 | .node = .{ .next = @ptrCast(@alignCast(group.token)) }, | |
| 466 | .node = undefined, | |
| 467 | 467 | .func = start, |
| 468 | 468 | .context_alignment = context_alignment, |
| 469 | 469 | .context_len = context.len, |
| 470 | 470 | }; |
| 471 | group.token = &gc.node; | |
| 472 | 471 | @memcpy(gc.contextPointer()[0..context.len], context); |
| 473 | 472 | |
| 474 | 473 | pool.mutex.lock(); |
| 475 | 474 | |
| 475 | // Append to the group linked list inside the mutex to make `Io.Group.async` thread-safe. | |
| 476 | gc.node = .{ .next = @ptrCast(@alignCast(group.token)) }; | |
| 477 | group.token = &gc.node; | |
| 478 | ||
| 476 | 479 | const thread_capacity = cpu_count - 1 + pool.concurrent_count; |
| 477 | 480 | |
| 478 | 481 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { |
| ... | ... | @@ -493,6 +496,8 @@ fn groupAsync( |
| 493 | 496 | pool.threads.appendAssumeCapacity(thread); |
| 494 | 497 | } |
| 495 | 498 | |
| 499 | // This needs to be done before unlocking the mutex to avoid a race with | |
| 500 | // the associated task finishing. | |
| 496 | 501 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 497 | 502 | std.Thread.WaitGroup.startStateless(group_state); |
| 498 | 503 | |
| ... | ... | @@ -500,21 +505,16 @@ fn groupAsync( |
| 500 | 505 | pool.cond.signal(); |
| 501 | 506 | } |
| 502 | 507 | |
| 503 | fn groupWait(userdata: ?*anyopaque, group: *Io.Group) void { | |
| 504 | if (builtin.single_threaded) return; | |
| 508 | fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { | |
| 505 | 509 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 506 | 510 | _ = pool; |
| 511 | ||
| 512 | if (builtin.single_threaded) return; | |
| 513 | ||
| 507 | 514 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); |
| 508 | 515 | const reset_event: *ResetEvent = @ptrCast(&group.context); |
| 509 | 516 | std.Thread.WaitGroup.waitStateless(group_state, reset_event); |
| 510 | } | |
| 511 | 517 | |
| 512 | fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void { | |
| 513 | if (builtin.single_threaded) return; | |
| 514 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | |
| 515 | _ = pool; | |
| 516 | const token = group.token.?; | |
| 517 | group.token = null; | |
| 518 | 518 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); |
| 519 | 519 | while (true) { |
| 520 | 520 | const gc: *GroupClosure = @fieldParentPtr("node", node); |
| ... | ... | @@ -523,6 +523,36 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void { |
| 523 | 523 | } |
| 524 | 524 | } |
| 525 | 525 | |
| 526 | fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { | |
| 527 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | |
| 528 | const gpa = pool.allocator; | |
| 529 | ||
| 530 | if (builtin.single_threaded) return; | |
| 531 | ||
| 532 | { | |
| 533 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); | |
| 534 | while (true) { | |
| 535 | const gc: *GroupClosure = @fieldParentPtr("node", node); | |
| 536 | gc.closure.requestCancel(); | |
| 537 | node = node.next orelse break; | |
| 538 | } | |
| 539 | } | |
| 540 | ||
| 541 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); | |
| 542 | const reset_event: *ResetEvent = @ptrCast(&group.context); | |
| 543 | std.Thread.WaitGroup.waitStateless(group_state, reset_event); | |
| 544 | ||
| 545 | { | |
| 546 | var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); | |
| 547 | while (true) { | |
| 548 | const gc: *GroupClosure = @fieldParentPtr("node", node); | |
| 549 | const node_next = node.next; | |
| 550 | gc.free(gpa); | |
| 551 | node = node_next orelse break; | |
| 552 | } | |
| 553 | } | |
| 554 | } | |
| 555 | ||
| 526 | 556 | fn await( |
| 527 | 557 | userdata: ?*anyopaque, |
| 528 | 558 | any_future: *Io.AnyFuture, |
| ... | ... | @@ -774,7 +804,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File |
| 774 | 804 | .SUCCESS => return nread, |
| 775 | 805 | .INTR => unreachable, |
| 776 | 806 | .INVAL => unreachable, |
| 777 | .FAULT => unreachable, | |
| 807 | .FAULT => |err| return errnoBug(err), | |
| 778 | 808 | .AGAIN => unreachable, // currently not support in WASI |
| 779 | 809 | .BADF => return error.NotOpenForReading, // can be a race condition |
| 780 | 810 | .IO => return error.InputOutput, |
| ... | ... | @@ -796,7 +826,7 @@ fn fileReadStreaming(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File |
| 796 | 826 | .SUCCESS => return @intCast(rc), |
| 797 | 827 | .INTR => continue, |
| 798 | 828 | .INVAL => unreachable, |
| 799 | .FAULT => unreachable, | |
| 829 | .FAULT => |err| return errnoBug(err), | |
| 800 | 830 | .SRCH => return error.ProcessNotFound, |
| 801 | 831 | .AGAIN => return error.WouldBlock, |
| 802 | 832 | .BADF => return error.NotOpenForReading, // can be a race condition |
| ... | ... | @@ -896,7 +926,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset |
| 896 | 926 | .SUCCESS => return nread, |
| 897 | 927 | .INTR => unreachable, |
| 898 | 928 | .INVAL => unreachable, |
| 899 | .FAULT => unreachable, | |
| 929 | .FAULT => |err| return errnoBug(err), | |
| 900 | 930 | .AGAIN => unreachable, |
| 901 | 931 | .BADF => return error.NotOpenForReading, // can be a race condition |
| 902 | 932 | .IO => return error.InputOutput, |
| ... | ... | @@ -922,7 +952,7 @@ fn fileReadPositional(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset |
| 922 | 952 | .SUCCESS => return @bitCast(rc), |
| 923 | 953 | .INTR => continue, |
| 924 | 954 | .INVAL => unreachable, |
| 925 | .FAULT => unreachable, | |
| 955 | .FAULT => |err| return errnoBug(err), | |
| 926 | 956 | .SRCH => return error.ProcessNotFound, |
| 927 | 957 | .AGAIN => return error.WouldBlock, |
| 928 | 958 | .BADF => return error.NotOpenForReading, // can be a race condition |
| ... | ... | @@ -969,18 +999,19 @@ fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: posi |
| 969 | 999 | }; |
| 970 | 1000 | } |
| 971 | 1001 | |
| 972 | fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp { | |
| 1002 | fn now(userdata: ?*anyopaque, clockid: posix.clockid_t) Io.NowError!Io.Timestamp { | |
| 973 | 1003 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 974 | 1004 | try pool.checkCancel(); |
| 975 | 1005 | const timespec = try posix.clock_gettime(clockid); |
| 976 | 1006 | return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec); |
| 977 | 1007 | } |
| 978 | 1008 | |
| 979 | fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void { | |
| 1009 | fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, timeout: Io.Timeout) Io.SleepError!void { | |
| 980 | 1010 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 981 | const deadline_nanoseconds: i96 = switch (deadline) { | |
| 1011 | const deadline_nanoseconds: i96 = switch (timeout) { | |
| 1012 | .none => std.math.maxInt(i96), | |
| 982 | 1013 | .duration => |duration| duration.nanoseconds, |
| 983 | .timestamp => |timestamp| @intFromEnum(timestamp), | |
| 1014 | .deadline => |deadline| @intFromEnum(deadline), | |
| 984 | 1015 | }; |
| 985 | 1016 | var timespec: posix.timespec = .{ |
| 986 | 1017 | .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)), |
| ... | ... | @@ -988,12 +1019,12 @@ fn sleep(userdata: ?*anyopaque, clockid: posix.clockid_t, deadline: Io.Deadline) |
| 988 | 1019 | }; |
| 989 | 1020 | while (true) { |
| 990 | 1021 | try pool.checkCancel(); |
| 991 | switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (deadline) { | |
| 992 | .duration => false, | |
| 993 | .timestamp => true, | |
| 1022 | switch (std.os.linux.E.init(std.os.linux.clock_nanosleep(clockid, .{ .ABSTIME = switch (timeout) { | |
| 1023 | .none, .duration => false, | |
| 1024 | .deadline => true, | |
| 994 | 1025 | } }, &timespec, &timespec))) { |
| 995 | 1026 | .SUCCESS => return, |
| 996 | .FAULT => unreachable, | |
| 1027 | .FAULT => |err| return errnoBug(err), | |
| 997 | 1028 | .INTR => {}, |
| 998 | 1029 | .INVAL => return error.UnsupportedClock, |
| 999 | 1030 | else => |err| return posix.unexpectedErrno(err), |
| ... | ... | @@ -1278,7 +1309,7 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n |
| 1278 | 1309 | fn netSend( |
| 1279 | 1310 | userdata: ?*anyopaque, |
| 1280 | 1311 | handle: Io.net.Socket.Handle, |
| 1281 | address: Io.net.IpAddress, | |
| 1312 | address: *const Io.net.IpAddress, | |
| 1282 | 1313 | data: []const u8, |
| 1283 | 1314 | ) Io.net.Socket.SendError!void { |
| 1284 | 1315 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| ... | ... | @@ -1293,15 +1324,15 @@ fn netSend( |
| 1293 | 1324 | fn netReceive( |
| 1294 | 1325 | userdata: ?*anyopaque, |
| 1295 | 1326 | handle: Io.net.Socket.Handle, |
| 1296 | address: Io.net.IpAddress, | |
| 1297 | 1327 | buffer: []u8, |
| 1298 | ) Io.net.Socket.ReceiveError!void { | |
| 1328 | timeout: Io.Timeout, | |
| 1329 | ) Io.net.Socket.ReceiveTimeoutError!Io.net.ReceivedMessage { | |
| 1299 | 1330 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 1300 | 1331 | try pool.checkCancel(); |
| 1301 | 1332 | |
| 1302 | 1333 | _ = handle; |
| 1303 | _ = address; | |
| 1304 | 1334 | _ = buffer; |
| 1335 | _ = timeout; | |
| 1305 | 1336 | @panic("TODO"); |
| 1306 | 1337 | } |
| 1307 | 1338 |
lib/std/Io/net.zig+32-10| ... | ... | @@ -134,13 +134,13 @@ pub const IpAddress = union(enum) { |
| 134 | 134 | } |
| 135 | 135 | } |
| 136 | 136 | |
| 137 | pub fn eql(a: IpAddress, b: IpAddress) bool { | |
| 138 | return switch (a) { | |
| 139 | .ip4 => |a_ip4| switch (b) { | |
| 137 | pub fn eql(a: *const IpAddress, b: *const IpAddress) bool { | |
| 138 | return switch (a.*) { | |
| 139 | .ip4 => |a_ip4| switch (b.*) { | |
| 140 | 140 | .ip4 => |b_ip4| a_ip4.eql(b_ip4), |
| 141 | 141 | else => false, |
| 142 | 142 | }, |
| 143 | .ip6 => |a_ip6| switch (b) { | |
| 143 | .ip6 => |a_ip6| switch (b.*) { | |
| 144 | 144 | .ip6 => |b_ip6| a_ip6.eql(b_ip6), |
| 145 | 145 | else => false, |
| 146 | 146 | }, |
| ... | ... | @@ -695,6 +695,11 @@ pub const Ip6Address = struct { |
| 695 | 695 | }; |
| 696 | 696 | }; |
| 697 | 697 | |
| 698 | pub const ReceivedMessage = struct { | |
| 699 | from: IpAddress, | |
| 700 | len: usize, | |
| 701 | }; | |
| 702 | ||
| 698 | 703 | pub const Interface = struct { |
| 699 | 704 | /// Value 0 indicates `none`. |
| 700 | 705 | index: u32, |
| ... | ... | @@ -816,14 +821,31 @@ pub const Socket = struct { |
| 816 | 821 | return io.vtable.netSend(io.userdata, s.handle, dest, data); |
| 817 | 822 | } |
| 818 | 823 | |
| 819 | pub const ReceiveError = error{} || Io.Cancelable; | |
| 824 | pub const ReceiveError = error{} || Io.UnexpectedError || Io.Cancelable; | |
| 820 | 825 | |
| 821 | /// Transfers `data` from `source`, connectionless. | |
| 826 | /// Waits for data. Connectionless. | |
| 822 | 827 | /// |
| 823 | /// Returned slice has same pointer as `buffer` with possibly shorter length. | |
| 824 | pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError![]u8 { | |
| 825 | const n = try io.vtable.netReceive(io.userdata, s.handle, source, buffer); | |
| 826 | return buffer[0..n]; | |
| 828 | /// See also: | |
| 829 | /// * `receiveTimeout` | |
| 830 | pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError!ReceivedMessage { | |
| 831 | return io.vtable.netReceive(io.userdata, s.handle, source, buffer, .none); | |
| 832 | } | |
| 833 | ||
| 834 | pub const ReceiveTimeoutError = ReceiveError || Io.Timeout.Error; | |
| 835 | ||
| 836 | /// Waits for data. Connectionless. | |
| 837 | /// | |
| 838 | /// Returns `error.Timeout` if no message arrives early enough. | |
| 839 | /// | |
| 840 | /// See also: | |
| 841 | /// * `receive` | |
| 842 | pub fn receiveTimeout( | |
| 843 | s: *const Socket, | |
| 844 | io: Io, | |
| 845 | buffer: []u8, | |
| 846 | timeout: Io.Timeout, | |
| 847 | ) ReceiveTimeoutError!ReceivedMessage { | |
| 848 | return io.vtable.netReceive(io.userdata, s.handle, buffer, timeout); | |
| 827 | 849 | } |
| 828 | 850 | }; |
| 829 | 851 |
lib/std/Io/net/HostName.zig+113-93| ... | ... | @@ -46,15 +46,13 @@ pub const LookupOptions = struct { |
| 46 | 46 | family: ?IpAddress.Family = null, |
| 47 | 47 | }; |
| 48 | 48 | |
| 49 | pub const LookupError = Io.Cancelable || Io.File.OpenError || Io.File.Reader.Error || error{ | |
| 49 | pub const LookupError = error{ | |
| 50 | 50 | UnknownHostName, |
| 51 | 51 | ResolvConfParseFailed, |
| 52 | // TODO remove from error set; retry a few times then report a different error | |
| 53 | TemporaryNameServerFailure, | |
| 54 | 52 | InvalidDnsARecord, |
| 55 | 53 | InvalidDnsAAAARecord, |
| 56 | 54 | NameServerFailure, |
| 57 | }; | |
| 55 | } || Io.NowError || IpAddress.BindError || Io.File.OpenError || Io.File.Reader.Error || Io.Cancelable; | |
| 58 | 56 | |
| 59 | 57 | pub const LookupResult = struct { |
| 60 | 58 | /// How many `LookupOptions.addresses_buffer` elements are populated. |
| ... | ... | @@ -185,7 +183,7 @@ fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult |
| 185 | 183 | return result; |
| 186 | 184 | } |
| 187 | 185 | |
| 188 | fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupResult { | |
| 186 | fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) LookupError!LookupResult { | |
| 189 | 187 | const rc = ResolvConf.init(io) catch return error.ResolvConfParseFailed; |
| 190 | 188 | |
| 191 | 189 | // Count dots, suppress search when >=ndots or name ends in |
| ... | ... | @@ -218,19 +216,17 @@ fn lookupDnsSearch(host_name: HostName, io: Io, options: LookupOptions) !LookupR |
| 218 | 216 | return lookupDns(io, lookup_canon_name, &rc, options); |
| 219 | 217 | } |
| 220 | 218 | |
| 221 | const DnsReply = struct { | |
| 222 | buf: [512]u8, | |
| 223 | len: usize, | |
| 224 | }; | |
| 225 | ||
| 226 | fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) !LookupResult { | |
| 219 | fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, options: LookupOptions) LookupError!LookupResult { | |
| 227 | 220 | const family_records: [2]struct { af: IpAddress.Family, rr: u8 } = .{ |
| 228 | 221 | .{ .af = .ip6, .rr = std.posix.RR.A }, |
| 229 | 222 | .{ .af = .ip4, .rr = std.posix.RR.AAAA }, |
| 230 | 223 | }; |
| 231 | 224 | var query_buffers: [2][280]u8 = undefined; |
| 225 | var answer_buffers: [2][512]u8 = undefined; | |
| 232 | 226 | var queries_buffer: [2][]const u8 = undefined; |
| 227 | var answers_buffer: [2][]const u8 = undefined; | |
| 233 | 228 | var nq: usize = 0; |
| 229 | var next_answer_buffer: usize = 0; | |
| 234 | 230 | |
| 235 | 231 | for (family_records) |fr| { |
| 236 | 232 | if (options.family != fr.af) { |
| ... | ... | @@ -241,41 +237,123 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio |
| 241 | 237 | } |
| 242 | 238 | } |
| 243 | 239 | |
| 240 | var ip4_mapped: [ResolvConf.max_nameservers]IpAddress = undefined; | |
| 241 | var any_ip6 = false; | |
| 242 | for (rc.nameservers(), &ip4_mapped) |*ns, *m| { | |
| 243 | m.* = .{ .ip6 = .fromAny(ns.*) }; | |
| 244 | any_ip6 = any_ip6 or ns.* == .ip6; | |
| 245 | } | |
| 246 | var socket = s: { | |
| 247 | if (any_ip6) ip6: { | |
| 248 | const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; | |
| 249 | const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) { | |
| 250 | error.AddressFamilyUnsupported => break :ip6, | |
| 251 | else => |e| return e, | |
| 252 | }; | |
| 253 | break :s socket; | |
| 254 | } | |
| 255 | any_ip6 = false; | |
| 256 | const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; | |
| 257 | const socket = try ip4_addr.bind(io, .{ .mode = .dgram }); | |
| 258 | break :s socket; | |
| 259 | }; | |
| 260 | defer socket.close(io); | |
| 261 | ||
| 262 | const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers(); | |
| 244 | 263 | const queries = queries_buffer[0..nq]; |
| 245 | var replies_buffer: [2]DnsReply = undefined; | |
| 246 | var replies: Io.Queue(DnsReply) = .init(&replies_buffer); | |
| 247 | try rc.sendMessage(io, queries, &replies); | |
| 248 | ||
| 249 | for (replies) |reply| { | |
| 250 | if (reply.len < 4 or (reply[3] & 15) == 2) return error.TemporaryNameServerFailure; | |
| 251 | if ((reply[3] & 15) == 3) return .empty; | |
| 252 | if ((reply[3] & 15) != 0) return error.UnknownHostName; | |
| 264 | const answers = answers_buffer[0..queries.len]; | |
| 265 | for (answers) |*answer| answer.len = 0; | |
| 266 | ||
| 267 | var now_ts = try io.now(.MONOTONIC); | |
| 268 | const final_ts = now_ts.addDuration(.seconds(rc.timeout_seconds)); | |
| 269 | const attempt_duration: Io.Duration = .{ | |
| 270 | .nanoseconds = std.time.ns_per_s * @as(usize, rc.timeout_seconds) / rc.attempts, | |
| 271 | }; | |
| 272 | ||
| 273 | send: while (now_ts.compare(.lt, final_ts)) : (now_ts = try io.now(.MONOTONIC)) { | |
| 274 | var group: Io.Group = .init; | |
| 275 | defer group.cancel(io); | |
| 276 | ||
| 277 | for (queries, answers) |query, *answer| { | |
| 278 | if (answer.len != 0) continue; | |
| 279 | for (mapped_nameservers) |*ns| { | |
| 280 | group.async(io, sendIgnoringResult, .{ io, socket.handle, ns, query }); | |
| 281 | } | |
| 282 | } | |
| 283 | ||
| 284 | const timeout: Io.Timeout = .{ .deadline = now_ts.addDuration(attempt_duration) }; | |
| 285 | ||
| 286 | while (true) { | |
| 287 | const buf = &answer_buffers[next_answer_buffer]; | |
| 288 | const reply = socket.receiveTimeout(io, buf, timeout) catch |err| switch (err) { | |
| 289 | error.Canceled => return error.Canceled, | |
| 290 | error.Timeout => continue :send, | |
| 291 | else => continue, | |
| 292 | }; | |
| 293 | ||
| 294 | // Ignore non-identifiable packets. | |
| 295 | if (reply.len < 4) continue; | |
| 296 | ||
| 297 | // Ignore replies from addresses we didn't send to. | |
| 298 | const ns = for (mapped_nameservers) |*ns| { | |
| 299 | if (reply.from.eql(ns)) break ns; | |
| 300 | } else { | |
| 301 | continue; | |
| 302 | }; | |
| 303 | ||
| 304 | const reply_msg = buf[0..reply.len]; | |
| 305 | ||
| 306 | // Find which query this answer goes with, if any. | |
| 307 | const query, const answer = for (queries, answers) |query, *answer| { | |
| 308 | if (reply_msg[0] == query[0] and reply_msg[1] == query[1]) break .{ query, answer }; | |
| 309 | } else { | |
| 310 | continue; | |
| 311 | }; | |
| 312 | if (answer.len != 0) continue; | |
| 313 | ||
| 314 | // Only accept positive or negative responses; retry immediately on | |
| 315 | // server failure, and ignore all other codes such as refusal. | |
| 316 | switch (reply_msg[3] & 15) { | |
| 317 | 0, 3 => { | |
| 318 | answer.* = reply_msg; | |
| 319 | next_answer_buffer += 1; | |
| 320 | if (next_answer_buffer == answers.len) break :send; | |
| 321 | }, | |
| 322 | 2 => { | |
| 323 | group.async(io, sendIgnoringResult, .{ io, socket.handle, ns, query }); | |
| 324 | continue; | |
| 325 | }, | |
| 326 | else => continue, | |
| 327 | } | |
| 328 | } | |
| 329 | } else { | |
| 330 | return error.NameServerFailure; | |
| 253 | 331 | } |
| 254 | 332 | |
| 255 | 333 | var addresses_len: usize = 0; |
| 256 | 334 | var canonical_name: ?HostName = null; |
| 257 | 335 | |
| 258 | for (replies) |reply| { | |
| 259 | var it = DnsResponse.init(reply) catch { | |
| 336 | for (answers) |answer| { | |
| 337 | var it = DnsResponse.init(answer) catch { | |
| 260 | 338 | // TODO accept a diagnostics struct and append warnings |
| 261 | 339 | continue; |
| 262 | 340 | }; |
| 263 | 341 | while (it.next() catch { |
| 264 | 342 | // TODO accept a diagnostics struct and append warnings |
| 265 | 343 | continue; |
| 266 | }) |answer| switch (answer.rr) { | |
| 344 | }) |record| switch (record.rr) { | |
| 267 | 345 | std.posix.RR.A => { |
| 268 | if (answer.data.len != 4) return error.InvalidDnsARecord; | |
| 346 | if (record.data.len != 4) return error.InvalidDnsARecord; | |
| 269 | 347 | options.addresses_buffer[addresses_len] = .{ .ip4 = .{ |
| 270 | .bytes = answer.data[0..4].*, | |
| 348 | .bytes = record.data[0..4].*, | |
| 271 | 349 | .port = options.port, |
| 272 | 350 | } }; |
| 273 | 351 | addresses_len += 1; |
| 274 | 352 | }, |
| 275 | 353 | std.posix.RR.AAAA => { |
| 276 | if (answer.data.len != 16) return error.InvalidDnsAAAARecord; | |
| 354 | if (record.data.len != 16) return error.InvalidDnsAAAARecord; | |
| 277 | 355 | options.addresses_buffer[addresses_len] = .{ .ip6 = .{ |
| 278 | .bytes = answer.data[0..16].*, | |
| 356 | .bytes = record.data[0..16].*, | |
| 279 | 357 | .port = options.port, |
| 280 | 358 | } }; |
| 281 | 359 | addresses_len += 1; |
| ... | ... | @@ -285,7 +363,7 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio |
| 285 | 363 | @panic("TODO"); |
| 286 | 364 | //var tmp: [256]u8 = undefined; |
| 287 | 365 | //// Returns len of compressed name. strlen to get canon name. |
| 288 | //_ = try posix.dn_expand(packet, answer.data, &tmp); | |
| 366 | //_ = try posix.dn_expand(packet, record.data, &tmp); | |
| 289 | 367 | //const canon_name = mem.sliceTo(&tmp, 0); |
| 290 | 368 | //if (isValidHostName(canon_name)) { |
| 291 | 369 | // ctx.canon.items.len = 0; |
| ... | ... | @@ -304,6 +382,10 @@ fn lookupDns(io: Io, lookup_canon_name: []const u8, rc: *const ResolvConf, optio |
| 304 | 382 | return error.NameServerFailure; |
| 305 | 383 | } |
| 306 | 384 | |
| 385 | fn sendIgnoringResult(io: Io, socket_handle: Io.net.Socket.Handle, dest: *const IpAddress, msg: []const u8) void { | |
| 386 | _ = io.vtable.netSend(io.userdata, socket_handle, dest, msg) catch {}; | |
| 387 | } | |
| 388 | ||
| 307 | 389 | fn lookupHosts(host_name: HostName, io: Io, options: LookupOptions) !LookupResult { |
| 308 | 390 | const file = Io.File.openAbsolute(io, "/etc/hosts", .{}) catch |err| switch (err) { |
| 309 | 391 | error.FileNotFound, |
| ... | ... | @@ -523,7 +605,7 @@ pub fn connectTcp(host_name: HostName, io: Io, port: u16) ConnectTcpError!Stream |
| 523 | 605 | pub const ResolvConf = struct { |
| 524 | 606 | attempts: u32, |
| 525 | 607 | ndots: u32, |
| 526 | timeout: Io.Duration, | |
| 608 | timeout_seconds: u32, | |
| 527 | 609 | nameservers_buffer: [max_nameservers]IpAddress, |
| 528 | 610 | nameservers_len: usize, |
| 529 | 611 | search_buffer: [max_len]u8, |
| ... | ... | @@ -539,7 +621,7 @@ pub const ResolvConf = struct { |
| 539 | 621 | .search_buffer = undefined, |
| 540 | 622 | .search_len = 0, |
| 541 | 623 | .ndots = 1, |
| 542 | .timeout = .seconds(5), | |
| 624 | .timeout_seconds = 5, | |
| 543 | 625 | .attempts = 2, |
| 544 | 626 | }; |
| 545 | 627 | |
| ... | ... | @@ -589,7 +671,7 @@ pub const ResolvConf = struct { |
| 589 | 671 | switch (std.meta.stringToEnum(Option, name) orelse continue) { |
| 590 | 672 | .ndots => rc.ndots = @min(value, 15), |
| 591 | 673 | .attempts => rc.attempts = @min(value, 10), |
| 592 | .timeout => rc.timeout = .seconds(@min(value, 60)), | |
| 674 | .timeout => rc.timeout_seconds = @min(value, 60), | |
| 593 | 675 | } |
| 594 | 676 | }, |
| 595 | 677 | .nameserver => { |
| ... | ... | @@ -621,68 +703,6 @@ pub const ResolvConf = struct { |
| 621 | 703 | fn nameservers(rc: *const ResolvConf) []const IpAddress { |
| 622 | 704 | return rc.nameservers_buffer[0..rc.nameservers_len]; |
| 623 | 705 | } |
| 624 | ||
| 625 | fn sendMessage( | |
| 626 | rc: *const ResolvConf, | |
| 627 | io: Io, | |
| 628 | queries: []const []const u8, | |
| 629 | replies: *Io.Queue(DnsReply), | |
| 630 | ) !void { | |
| 631 | var ip4_mapped: [ResolvConf.max_nameservers]IpAddress = undefined; | |
| 632 | var any_ip6 = false; | |
| 633 | for (rc.nameservers(), &ip4_mapped) |*ns, *m| { | |
| 634 | m.* = .{ .ip6 = .fromAny(ns.*) }; | |
| 635 | any_ip6 = any_ip6 or ns.* == .ip6; | |
| 636 | } | |
| 637 | ||
| 638 | const socket = s: { | |
| 639 | if (any_ip6) ip6: { | |
| 640 | const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; | |
| 641 | const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) { | |
| 642 | error.AddressFamilyUnsupported => break :ip6, | |
| 643 | else => |e| return e, | |
| 644 | }; | |
| 645 | break :s socket; | |
| 646 | } | |
| 647 | any_ip6 = false; | |
| 648 | const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; | |
| 649 | const socket = try ip4_addr.bind(io, .{ .mode = .dgram }); | |
| 650 | break :s socket; | |
| 651 | }; | |
| 652 | defer socket.close(); | |
| 653 | ||
| 654 | const mapped_nameservers = if (any_ip6) ip4_mapped[0..rc.nameservers_len] else rc.nameservers(); | |
| 655 | ||
| 656 | var group: Io.Group = .init; | |
| 657 | defer group.cancel(); | |
| 658 | ||
| 659 | for (queries) |query| { | |
| 660 | for (mapped_nameservers) |*ns| { | |
| 661 | group.async(sendOneMessage, .{ io, query, ns }); | |
| 662 | } | |
| 663 | } | |
| 664 | ||
| 665 | const deadline: Io.Deadline = .fromDuration(rc.timeout); | |
| 666 | ||
| 667 | for (0..queries.len) |_| { | |
| 668 | const msg = socket.receiveDeadline(deadline) catch |err| switch (err) { | |
| 669 | error.Timeout => return error.Timeout, | |
| 670 | error.Canceled => return error.Canceled, | |
| 671 | else => continue, | |
| 672 | }; | |
| 673 | _ = msg; | |
| 674 | _ = replies; | |
| 675 | @panic("TODO check msg for dns reply and put into replies queue"); | |
| 676 | } | |
| 677 | } | |
| 678 | ||
| 679 | fn sendOneMessage( | |
| 680 | io: Io, | |
| 681 | query: []const u8, | |
| 682 | ns: *const IpAddress, | |
| 683 | ) void { | |
| 684 | io.vtable.netSend(io.userdata, ns.*, &.{query}) catch |err| switch (err) {}; | |
| 685 | } | |
| 686 | 706 | }; |
| 687 | 707 | |
| 688 | 708 | test ResolvConf { |
| ... | ... | @@ -702,7 +722,7 @@ test ResolvConf { |
| 702 | 722 | .search_buffer = undefined, |
| 703 | 723 | .search_len = 0, |
| 704 | 724 | .ndots = 1, |
| 705 | .timeout = .seconds(5), | |
| 725 | .timeout_seconds = 5, | |
| 706 | 726 | .attempts = 2, |
| 707 | 727 | }; |
| 708 | 728 |