Commit be1ae430a1

Andrew Kelley <andrew@ziglang.org>
2025-10-14 00:53:19
std.Io.Threaded.netReadPosix: support cancelation
1 parent 71ff6e0
Changed files (3)
lib/std/Io/net.zig
@@ -1087,13 +1087,18 @@ pub const Stream = struct {
         stream: Stream,
         err: ?Error,
 
-        pub const Error = std.posix.ReadError || error{
-            SocketNotBound,
-            MessageTooBig,
-            NetworkSubsystemFailed,
+        pub const Error = error{
+            SystemResources,
+            BrokenPipe,
             ConnectionResetByPeer,
+            ConnectionTimedOut,
             SocketUnconnected,
-        } || Io.Cancelable || Io.Writer.Error || error{EndOfStream};
+            /// The file descriptor does not hold the required rights to read
+            /// from it.
+            AccessDenied,
+            NetworkDown,
+            EndOfStream,
+        } || Io.Cancelable || Io.UnexpectedError;
 
         pub fn init(stream: Stream, io: Io, buffer: []u8) Reader {
             return .{
@@ -1140,7 +1145,7 @@ pub const Stream = struct {
             ConnectionResetByPeer,
             SocketNotBound,
             MessageTooBig,
-            NetworkSubsystemFailed,
+            NetworkDown,
             SystemResources,
             SocketUnconnected,
             Unexpected,
lib/std/Io/Threaded.zig
@@ -1988,7 +1988,7 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: Io.net.Socket.Handle) Io.net
 
 fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.net.Stream.Reader.Error!usize {
     const pool: *Pool = @ptrCast(@alignCast(userdata));
-    try pool.checkCancel();
+    const fd = stream.socket.handle;
 
     var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
     var i: usize = 0;
@@ -2001,9 +2001,54 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n
     }
     const dest = iovecs_buffer[0..i];
     assert(dest[0].len > 0);
-    const n = try posix.readv(stream.socket.handle, dest);
-    if (n == 0) return error.EndOfStream;
-    return n;
+
+    if (native_os == .wasi and !builtin.link_libc) while (true) {
+        try pool.checkCancel();
+        var n: usize = undefined;
+        switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) {
+            .SUCCESS => {
+                if (n == 0) return error.EndOfStream;
+                return n;
+            },
+            .INTR => continue,
+            .INVAL => |err| return errnoBug(err),
+            .FAULT => |err| return errnoBug(err),
+            .AGAIN => |err| return errnoBug(err),
+            .BADF => |err| return errnoBug(err),
+            .NOBUFS => return error.SystemResources,
+            .NOMEM => return error.SystemResources,
+            .NOTCONN => return error.SocketUnconnected,
+            .CONNRESET => return error.ConnectionResetByPeer,
+            .TIMEDOUT => return error.ConnectionTimedOut,
+            .NOTCAPABLE => return error.AccessDenied,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    };
+
+    while (true) {
+        try pool.checkCancel();
+        const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len));
+        switch (posix.errno(rc)) {
+            .SUCCESS => {
+                const n: usize = @intCast(rc);
+                if (n == 0) return error.EndOfStream;
+                return n;
+            },
+            .INTR => continue,
+            .INVAL => |err| return errnoBug(err),
+            .FAULT => |err| return errnoBug(err),
+            .AGAIN => |err| return errnoBug(err),
+            .BADF => |err| return errnoBug(err), // Always a race condition.
+            .NOBUFS => return error.SystemResources,
+            .NOMEM => return error.SystemResources,
+            .NOTCONN => return error.SocketUnconnected,
+            .CONNRESET => return error.ConnectionResetByPeer,
+            .TIMEDOUT => return error.ConnectionTimedOut,
+            .PIPE => return error.BrokenPipe,
+            .NETDOWN => return error.NetworkDown,
+            else => |err| return posix.unexpectedErrno(err),
+        }
+    }
 }
 
 const have_sendmmsg = builtin.os.tag == .linux;
@@ -2071,7 +2116,7 @@ fn netSendOne(
                     .WSAEHOSTUNREACH => return error.NetworkUnreachable,
                     // TODO: WSAEINPROGRESS, WSAEINTR
                     .WSAEINVAL => unreachable,
-                    .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                    .WSAENETDOWN => return error.NetworkDown,
                     .WSAENETRESET => return error.ConnectionResetByPeer,
                     .WSAENETUNREACH => return error.NetworkUnreachable,
                     .WSAENOTCONN => return error.SocketUnconnected,
@@ -2114,7 +2159,7 @@ fn netSendOne(
             .HOSTUNREACH => return error.NetworkUnreachable,
             .NETUNREACH => return error.NetworkUnreachable,
             .NOTCONN => return error.SocketUnconnected,
-            .NETDOWN => return error.NetworkSubsystemFailed,
+            .NETDOWN => return error.NetworkDown,
             else => |err| return posix.unexpectedErrno(err),
         }
     }
lib/std/posix.zig
@@ -3613,7 +3613,7 @@ pub const ShutdownError = error{
     BlockingOperationInProgress,
 
     /// The network subsystem has failed.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     /// The socket is not connected (connection-oriented sockets only).
     SocketUnconnected,
@@ -3635,7 +3635,7 @@ pub fn shutdown(sock: socket_t, how: ShutdownHow) ShutdownError!void {
             .WSAECONNRESET => return error.ConnectionResetByPeer,
             .WSAEINPROGRESS => return error.BlockingOperationInProgress,
             .WSAEINVAL => unreachable,
-            .WSAENETDOWN => return error.NetworkSubsystemFailed,
+            .WSAENETDOWN => return error.NetworkDown,
             .WSAENOTCONN => return error.SocketUnconnected,
             .WSAENOTSOCK => unreachable,
             .WSANOTINITIALISED => unreachable,
@@ -3697,7 +3697,7 @@ pub const BindError = error{
     ReadOnlyFileSystem,
 
     /// The network subsystem has failed.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     FileDescriptorNotASocket,
 
@@ -3718,7 +3718,7 @@ pub fn bind(sock: socket_t, addr: *const sockaddr, len: socklen_t) BindError!voi
                 .WSAEFAULT => unreachable, // invalid pointers
                 .WSAEINVAL => return error.AlreadyBound,
                 .WSAENOBUFS => return error.SystemResources,
-                .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                .WSAENETDOWN => return error.NetworkDown,
                 else => |err| return windows.unexpectedWSAError(err),
             }
             unreachable;
@@ -3763,7 +3763,7 @@ pub const ListenError = error{
     OperationNotSupported,
 
     /// The network subsystem has failed.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     /// Ran out of system resources
     /// On Windows it can either run out of socket descriptors or buffer space
@@ -3782,7 +3782,7 @@ pub fn listen(sock: socket_t, backlog: u31) ListenError!void {
         if (rc == windows.ws2_32.SOCKET_ERROR) {
             switch (windows.ws2_32.WSAGetLastError()) {
                 .WSANOTINITIALISED => unreachable, // not initialized WSA
-                .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                .WSAENETDOWN => return error.NetworkDown,
                 .WSAEADDRINUSE => return error.AddressInUse,
                 .WSAEISCONN => return error.AlreadyConnected,
                 .WSAEINVAL => return error.SocketNotBound,
@@ -3840,7 +3840,7 @@ pub const AcceptError = error{
     ConnectionResetByPeer,
 
     /// The network subsystem has failed.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     /// The referenced socket is not a type that supports connection-oriented service.
     OperationNotSupported,
@@ -3893,7 +3893,7 @@ pub fn accept(
                     .WSAENOTSOCK => return error.FileDescriptorNotASocket,
                     .WSAEINVAL => return error.SocketNotListening,
                     .WSAEMFILE => return error.ProcessFdQuotaExceeded,
-                    .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                    .WSAENETDOWN => return error.NetworkDown,
                     .WSAENOBUFS => return error.FileDescriptorNotASocket,
                     .WSAEOPNOTSUPP => return error.OperationNotSupported,
                     .WSAEWOULDBLOCK => return error.WouldBlock,
@@ -3964,7 +3964,7 @@ fn setSockFlags(sock: socket_t, flags: u32) !void {
             if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) {
                 switch (windows.ws2_32.WSAGetLastError()) {
                     .WSANOTINITIALISED => unreachable,
-                    .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                    .WSAENETDOWN => return error.NetworkDown,
                     .WSAENOTSOCK => return error.FileDescriptorNotASocket,
                     // TODO: handle more errors
                     else => |err| return windows.unexpectedWSAError(err),
@@ -4105,7 +4105,7 @@ pub const GetSockNameError = error{
     SystemResources,
 
     /// The network subsystem has failed.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     /// Socket hasn't been bound yet
     SocketNotBound,
@@ -4119,7 +4119,7 @@ pub fn getsockname(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
         if (rc == windows.ws2_32.SOCKET_ERROR) {
             switch (windows.ws2_32.WSAGetLastError()) {
                 .WSANOTINITIALISED => unreachable,
-                .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                .WSAENETDOWN => return error.NetworkDown,
                 .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
                 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
                 .WSAEINVAL => return error.SocketNotBound,
@@ -4148,7 +4148,7 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock
         if (rc == windows.ws2_32.SOCKET_ERROR) {
             switch (windows.ws2_32.WSAGetLastError()) {
                 .WSANOTINITIALISED => unreachable,
-                .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                .WSAENETDOWN => return error.NetworkDown,
                 .WSAEFAULT => unreachable, // addr or addrlen have invalid pointers or addrlen points to an incorrect value
                 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
                 .WSAEINVAL => return error.SocketNotBound,
@@ -6057,7 +6057,7 @@ pub const SendError = error{
     NetworkUnreachable,
 
     /// The local network interface used to reach the destination is down.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     /// The destination address is not listening.
     ConnectionRefused,
@@ -6106,7 +6106,7 @@ pub fn sendmsg(
                     .WSAEHOSTUNREACH => return error.NetworkUnreachable,
                     // TODO: WSAEINPROGRESS, WSAEINTR
                     .WSAEINVAL => unreachable,
-                    .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                    .WSAENETDOWN => return error.NetworkDown,
                     .WSAENETRESET => return error.ConnectionResetByPeer,
                     .WSAENETUNREACH => return error.NetworkUnreachable,
                     .WSAENOTCONN => return error.SocketUnconnected,
@@ -6146,7 +6146,7 @@ pub fn sendmsg(
                 .HOSTUNREACH => return error.NetworkUnreachable,
                 .NETUNREACH => return error.NetworkUnreachable,
                 .NOTCONN => return error.SocketUnconnected,
-                .NETDOWN => return error.NetworkSubsystemFailed,
+                .NETDOWN => return error.NetworkDown,
                 else => |err| return unexpectedErrno(err),
             }
         }
@@ -6209,7 +6209,7 @@ pub fn sendto(
                 .WSAEHOSTUNREACH => return error.NetworkUnreachable,
                 // TODO: WSAEINPROGRESS, WSAEINTR
                 .WSAEINVAL => unreachable,
-                .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                .WSAENETDOWN => return error.NetworkDown,
                 .WSAENETRESET => return error.ConnectionResetByPeer,
                 .WSAENETUNREACH => return error.NetworkUnreachable,
                 .WSAENOTCONN => return error.SocketUnconnected,
@@ -6251,7 +6251,7 @@ pub fn sendto(
             .HOSTUNREACH => return error.NetworkUnreachable,
             .NETUNREACH => return error.NetworkUnreachable,
             .NOTCONN => return error.SocketUnconnected,
-            .NETDOWN => return error.NetworkSubsystemFailed,
+            .NETDOWN => return error.NetworkDown,
             else => |err| return unexpectedErrno(err),
         }
     }
@@ -6390,7 +6390,7 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len
 
 pub const PollError = error{
     /// The network subsystem has failed.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     /// The kernel had no space to allocate file descriptor tables.
     SystemResources,
@@ -6401,7 +6401,7 @@ pub fn poll(fds: []pollfd, timeout: i32) PollError!usize {
         switch (windows.poll(fds.ptr, @intCast(fds.len), timeout)) {
             windows.ws2_32.SOCKET_ERROR => switch (windows.ws2_32.WSAGetLastError()) {
                 .WSANOTINITIALISED => unreachable,
-                .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                .WSAENETDOWN => return error.NetworkDown,
                 .WSAENOBUFS => return error.SystemResources,
                 // TODO: handle more errors
                 else => |err| return windows.unexpectedWSAError(err),
@@ -6473,7 +6473,7 @@ pub const RecvFromError = error{
     MessageTooBig,
 
     /// The network subsystem has failed.
-    NetworkSubsystemFailed,
+    NetworkDown,
 
     /// The socket is not connected (connection-oriented sockets only).
     SocketUnconnected,
@@ -6504,7 +6504,7 @@ pub fn recvfrom(
                     .WSAECONNRESET => return error.ConnectionResetByPeer,
                     .WSAEINVAL => return error.SocketNotBound,
                     .WSAEMSGSIZE => return error.MessageTooBig,
-                    .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                    .WSAENETDOWN => return error.NetworkDown,
                     .WSAENOTCONN => return error.SocketUnconnected,
                     .WSAEWOULDBLOCK => return error.WouldBlock,
                     .WSAETIMEDOUT => return error.ConnectionTimedOut,
@@ -6578,7 +6578,7 @@ pub fn recvmsg(
             .PIPE => return error.BrokenPipe,
             .OPNOTSUPP => unreachable, // Some bit in the flags argument is inappropriate for the socket type.
             .CONNRESET => return error.ConnectionResetByPeer,
-            .NETDOWN => return error.NetworkSubsystemFailed,
+            .NETDOWN => return error.NetworkDown,
             else => |err| return unexpectedErrno(err),
         }
     }
@@ -6601,7 +6601,7 @@ pub const SetSockOptError = error{
     PermissionDenied,
 
     OperationNotSupported,
-    NetworkSubsystemFailed,
+    NetworkDown,
     FileDescriptorNotASocket,
     SocketNotBound,
     NoDevice,
@@ -6614,7 +6614,7 @@ pub fn setsockopt(fd: socket_t, level: i32, optname: u32, opt: []const u8) SetSo
         if (rc == windows.ws2_32.SOCKET_ERROR) {
             switch (windows.ws2_32.WSAGetLastError()) {
                 .WSANOTINITIALISED => unreachable,
-                .WSAENETDOWN => return error.NetworkSubsystemFailed,
+                .WSAENETDOWN => return error.NetworkDown,
                 .WSAEFAULT => unreachable,
                 .WSAENOTSOCK => return error.FileDescriptorNotASocket,
                 .WSAEINVAL => return error.SocketNotBound,