From e8a2e6578a3f5e4cd82eb59388e49e152728791e Mon Sep 17 00:00:00 2001 From: Lukas Lalinsky Date: Sat, 27 Dec 2025 10:06:21 +0100 Subject: [PATCH] Add std.Io.net.Stream.shutdown --- lib/std/Io.zig | 1 + lib/std/Io/Kqueue.zig | 13 ++++++ lib/std/Io/Threaded.zig | 88 +++++++++++++++++++++++++++++++++++++++++ lib/std/Io/net.zig | 14 +++++++ lib/std/Io/net/test.zig | 2 + 5 files changed, 118 insertions(+) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 7c3aa98e168fdbe8e1ea612dee5693ba7044b31f..77eedccb743b5f836804e7a73c94c17fd5672e5e 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -734,6 +734,7 @@ pub const VTable = struct { netWrite: *const fn (?*anyopaque, dest: net.Socket.Handle, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, netWriteFile: *const fn (?*anyopaque, net.Socket.Handle, header: []const u8, *Io.File.Reader, Io.Limit) net.Stream.Writer.WriteFileError!usize, netClose: *const fn (?*anyopaque, handle: []const net.Socket.Handle) void, + netShutdown: *const fn (?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void, netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void, diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index 26b8298cabb85e6ec34cc93424881541f781c1c8..df9fa1dee68a9deaf03e5a7e074848b7554b7e45 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -900,6 +900,7 @@ pub fn io(k: *Kqueue) Io { .netConnectIp = netConnectIp, .netConnectUnix = netConnectUnix, .netClose = netClose, + .netShutdown = netShutdown, .netRead = netRead, .netWrite = netWrite, .netSend = netSend, @@ -1549,12 +1550,22 @@ fn netWrite(userdata: ?*anyopaque, dest: net.Socket.Handle, header: []const u8, _ = splat; @panic("TODO"); } + fn netClose(userdata: ?*anyopaque, handle: net.Socket.Handle) void { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = handle; @panic("TODO"); } + +fn netShutdown(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { + const k: *Kqueue = @ptrCast(@alignCast(userdata)); + _ = k; + _ = handle; + _ = how; + @panic("TODO"); +} + fn netInterfaceNameResolve( userdata: ?*anyopaque, name: *const net.Interface.Name, @@ -1564,12 +1575,14 @@ fn netInterfaceNameResolve( _ = name; @panic("TODO"); } + fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { const k: *Kqueue = @ptrCast(@alignCast(userdata)); _ = k; _ = interface; @panic("TODO"); } + fn netLookup( userdata: ?*anyopaque, host_name: net.HostName, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9e758be9fd4b34721ac6de70e9cd601bea306fd8..3a1b5145610c9042fe8413a4283457be195cf7a5 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -891,6 +891,10 @@ pub fn io(t: *Threaded) Io { else => netConnectUnixPosix, }, .netClose = netClose, + .netShutdown = switch (native_os) { + .windows => netShutdownWindows, + else => netShutdownPosix, + }, .netRead = switch (native_os) { .windows => netReadWindows, else => netReadPosix, @@ -1007,6 +1011,7 @@ pub fn ioBasic(t: *Threaded) Io { .netConnectIp = netConnectIpUnavailable, .netConnectUnix = netConnectUnixUnavailable, .netClose = netCloseUnavailable, + .netShutdown = netShutdownUnavailable, .netRead = netReadUnavailable, .netWrite = netWriteUnavailable, .netWriteFile = netWriteFileUnavailable, @@ -10390,6 +10395,89 @@ fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle unreachable; // How you gonna close something that was impossible to open? } +fn netShutdownPosix(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { + if (!have_networking) return error.NetworkDown; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + const posix_how: i32 = switch (how) { + .recv => posix.SHUT.RD, + .send => posix.SHUT.WR, + .both => posix.SHUT.RDWR, + }; + + try current_thread.beginSyscall(); + while (true) { + switch (posix.errno(posix.system.shutdown(handle, posix_how))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err), + .NOTCONN => return error.SocketUnconnected, + .NOBUFS => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } +} + +fn netShutdownWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, how: net.ShutdownHow) net.ShutdownError!void { + if (!have_networking) return error.NetworkDown; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + const wsa_how: i32 = switch (how) { + .recv => ws2_32.SD_RECEIVE, + .send => ws2_32.SD_SEND, + .both => ws2_32.SD_BOTH, + }; + + try current_thread.beginSyscall(); + while (true) { + const rc = ws2_32.shutdown(handle, wsa_how); + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + return; + } + switch (ws2_32.WSAGetLastError()) { + .EINTR => { + try current_thread.checkCancel(); + continue; + }, + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ECONNABORTED => return error.ConnectionAborted, + .ECONNRESET => return error.ConnectionResetByPeer, + .ENETDOWN => return error.NetworkDown, + .ENOTCONN => return error.SocketUnconnected, + .EINVAL, .ENOTSOCK => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, + } + } +} + +fn netShutdownUnavailable(_: ?*anyopaque, _: net.Socket.Handle, _: net.ShutdownHow) net.ShutdownError!void { + unreachable; // How you gonna shutdown something that was impossible to open? +} + fn netInterfaceNameResolve( userdata: ?*anyopaque, name: *const net.Interface.Name, diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 8b1523fbd3068410cff2e0be4dff83cae0de6f82..76a581180d10ae2f256fe407413a2f15911e4f70 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -954,6 +954,16 @@ pub const SendFlags = packed struct(u8) { _: u3 = 0, }; +pub const ShutdownHow = enum { recv, send, both }; + +pub const ShutdownError = error{ + ConnectionAborted, + ConnectionResetByPeer, + NetworkDown, + SocketUnconnected, + SystemResources, +} || Io.UnexpectedError || Io.Cancelable; + pub const Interface = struct { /// Value 0 indicates `none`. index: u32, @@ -1191,6 +1201,10 @@ pub const Stream = struct { io.vtable.netClose(io.userdata, (&s.socket.handle)[0..1]); } + pub fn shutdown(s: *const Stream, io: Io, how: ShutdownHow) ShutdownError!void { + return io.vtable.netShutdown(io.userdata, s.socket.handle, how); + } + pub const Reader = struct { io: Io, interface: Io.Reader, diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 6ef8c15f4f3f5c8e144f8943d54a8607671c6697..45c26f95401d0a286414dc659ed7306db0072123 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -346,6 +346,8 @@ test "non-blocking tcp server" { const len = try socket_file.read(&buf); const msg = buf[0..len]; try testing.expect(mem.eql(u8, msg, "hello from server\n")); + + try stream.shutdown(io, .both); } test "decompress compressed DNS name" { -- 2.54.0