authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-16 23:40:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:50-07:00
logcf6fa219fd05b9f2c01e85557bcd140e72802459
tree7384deba8832c91ae257180835cc3c957912927f
parentd4215ffaa04b976400bd597cca0cca8182068bf6

std.Io.Threaded: fix netWrite cancellation

Move std.posix logic over rather than calling into it.

2 files changed, 87 insertions(+), 32 deletions(-)

lib/std/Io/Threaded.zig+61-25
...@@ -250,6 +250,19 @@ pub fn io(t: *Threaded) Io {...@@ -250,6 +250,19 @@ pub fn io(t: *Threaded) Io {
250 };250 };
251}251}
252252
253const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩
254const have_accept4 = !socket_flags_unsupported;
255const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
256const have_networking = builtin.os.tag != .wasi;
257const have_flock = @TypeOf(posix.system.flock) != void;
258const have_sendmmsg = builtin.os.tag == .linux;
259
260const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
261const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
262const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
263const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
264const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
265
253/// Trailing data:266/// Trailing data:
254/// 1. context267/// 1. context
255/// 2. result268/// 2. result
...@@ -1143,13 +1156,6 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File....@@ -1143,13 +1156,6 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.
1143 }1156 }
1144}1157}
11451158
1146const have_flock = @TypeOf(posix.system.flock) != void;
1147const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
1148const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
1149const fstatat_sym = if (posix.lfs64_abi) posix.system.fstatat64 else posix.system.fstatat;
1150const lseek_sym = if (posix.lfs64_abi) posix.system.lseek64 else posix.system.lseek;
1151const preadv_sym = if (posix.lfs64_abi) posix.system.preadv64 else posix.system.preadv;
1152
1153fn dirAccessPosix(1159fn dirAccessPosix(
1154 userdata: ?*anyopaque,1160 userdata: ?*anyopaque,
1155 dir: Io.Dir,1161 dir: Io.Dir,
...@@ -1277,8 +1283,7 @@ fn dirCreateFilePosix(...@@ -1277,8 +1283,7 @@ fn dirCreateFilePosix(
1277 // Use the O locking flags if the os supports them to acquire the lock1283 // Use the O locking flags if the os supports them to acquire the lock
1278 // atomically. Note that the NONBLOCK flag is removed after the openat()1284 // atomically. Note that the NONBLOCK flag is removed after the openat()
1279 // call is successful.1285 // call is successful.
1280 const has_flock_open_flags = @hasField(posix.O, "EXLOCK");1286 if (have_flock_open_flags) switch (flags.lock) {
1281 if (has_flock_open_flags) switch (flags.lock) {
1282 .none => {},1287 .none => {},
1283 .shared => {1288 .shared => {
1284 os_flags.SHLOCK = true;1289 os_flags.SHLOCK = true;
...@@ -1328,7 +1333,7 @@ fn dirCreateFilePosix(...@@ -1328,7 +1333,7 @@ fn dirCreateFilePosix(
1328 };1333 };
1329 errdefer posix.close(fd);1334 errdefer posix.close(fd);
13301335
1331 if (have_flock and !has_flock_open_flags and flags.lock != .none) {1336 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
1332 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;1337 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
1333 const lock_flags = switch (flags.lock) {1338 const lock_flags = switch (flags.lock) {
1334 .none => unreachable,1339 .none => unreachable,
...@@ -1352,7 +1357,7 @@ fn dirCreateFilePosix(...@@ -1352,7 +1357,7 @@ fn dirCreateFilePosix(
1352 }1357 }
1353 }1358 }
13541359
1355 if (has_flock_open_flags and flags.lock_nonblocking) {1360 if (have_flock_open_flags and flags.lock_nonblocking) {
1356 var fl_flags: usize = while (true) {1361 var fl_flags: usize = while (true) {
1357 try t.checkCancel();1362 try t.checkCancel();
1358 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));1363 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
...@@ -1476,8 +1481,7 @@ fn dirOpenFile(...@@ -1476,8 +1481,7 @@ fn dirOpenFile(
14761481
1477 // Use the O locking flags if the os supports them to acquire the lock1482 // Use the O locking flags if the os supports them to acquire the lock
1478 // atomically.1483 // atomically.
1479 const has_flock_open_flags = @hasField(posix.O, "EXLOCK");1484 if (have_flock_open_flags) {
1480 if (has_flock_open_flags) {
1481 // Note that the NONBLOCK flag is removed after the openat() call1485 // Note that the NONBLOCK flag is removed after the openat() call
1482 // is successful.1486 // is successful.
1483 switch (flags.lock) {1487 switch (flags.lock) {
...@@ -1530,7 +1534,7 @@ fn dirOpenFile(...@@ -1530,7 +1534,7 @@ fn dirOpenFile(
1530 };1534 };
1531 errdefer posix.close(fd);1535 errdefer posix.close(fd);
15321536
1533 if (have_flock and !has_flock_open_flags and flags.lock != .none) {1537 if (have_flock and !have_flock_open_flags and flags.lock != .none) {
1534 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;1538 const lock_nonblocking: i32 = if (flags.lock_nonblocking) posix.LOCK.NB else 0;
1535 const lock_flags = switch (flags.lock) {1539 const lock_flags = switch (flags.lock) {
1536 .none => unreachable,1540 .none => unreachable,
...@@ -1554,7 +1558,7 @@ fn dirOpenFile(...@@ -1554,7 +1558,7 @@ fn dirOpenFile(
1554 }1558 }
1555 }1559 }
15561560
1557 if (has_flock_open_flags and flags.lock_nonblocking) {1561 if (have_flock_open_flags and flags.lock_nonblocking) {
1558 var fl_flags: usize = while (true) {1562 var fl_flags: usize = while (true) {
1559 try t.checkCancel();1563 try t.checkCancel();
1560 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));1564 const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0));
...@@ -1954,7 +1958,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {...@@ -1954,7 +1958,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
1954 var ns: std.os.wasi.timestamp_t = undefined;1958 var ns: std.os.wasi.timestamp_t = undefined;
1955 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);1959 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
1956 if (err != .SUCCESS) return error.Unexpected;1960 if (err != .SUCCESS) return error.Unexpected;
1957 return ns;1961 return .fromNanoseconds(ns);
1958}1962}
19591963
1960fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {1964fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
...@@ -2004,7 +2008,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {...@@ -2004,7 +2008,7 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
20042008
2005 const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t.io())) |d| .{2009 const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t.io())) |d| .{
2006 .id = clockToWasi(d.clock),2010 .id = clockToWasi(d.clock),
2007 .timeout = std.math.lossyCast(u64, d.duration.nanoseconds),2011 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds),
2008 .precision = 0,2012 .precision = 0,
2009 .flags = 0,2013 .flags = 0,
2010 } else .{2014 } else .{
...@@ -2083,6 +2087,7 @@ fn netListenIpPosix(...@@ -2083,6 +2087,7 @@ fn netListenIpPosix(
2083 address: IpAddress,2087 address: IpAddress,
2084 options: IpAddress.ListenOptions,2088 options: IpAddress.ListenOptions,
2085) IpAddress.ListenError!net.Server {2089) IpAddress.ListenError!net.Server {
2090 if (!have_networking) return error.NetworkDown;
2086 const t: *Threaded = @ptrCast(@alignCast(userdata));2091 const t: *Threaded = @ptrCast(@alignCast(userdata));
2087 const family = posixAddressFamily(&address);2092 const family = posixAddressFamily(&address);
2088 const socket_fd = try openSocketPosix(t, family, .{2093 const socket_fd = try openSocketPosix(t, family, .{
...@@ -2230,6 +2235,7 @@ fn posixConnect(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sock...@@ -2230,6 +2235,7 @@ fn posixConnect(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sock
2230 .ACCES => return error.AccessDenied,2235 .ACCES => return error.AccessDenied,
2231 .PERM => |err| return errnoBug(err),2236 .PERM => |err| return errnoBug(err),
2232 .NOENT => |err| return errnoBug(err),2237 .NOENT => |err| return errnoBug(err),
2238 .NETDOWN => return error.NetworkDown,
2233 else => |err| return posix.unexpectedErrno(err),2239 else => |err| return posix.unexpectedErrno(err),
2234 }2240 }
2235 }2241 }
...@@ -2306,6 +2312,7 @@ fn netConnectIpPosix(...@@ -2306,6 +2312,7 @@ fn netConnectIpPosix(
2306 address: *const IpAddress,2312 address: *const IpAddress,
2307 options: IpAddress.ConnectOptions,2313 options: IpAddress.ConnectOptions,
2308) IpAddress.ConnectError!net.Stream {2314) IpAddress.ConnectError!net.Stream {
2315 if (!have_networking) return error.NetworkDown;
2309 if (options.timeout != .none) @panic("TODO");2316 if (options.timeout != .none) @panic("TODO");
2310 const t: *Threaded = @ptrCast(@alignCast(userdata));2317 const t: *Threaded = @ptrCast(@alignCast(userdata));
2311 const family = posixAddressFamily(address);2318 const family = posixAddressFamily(address);
...@@ -2346,6 +2353,7 @@ fn netBindIpPosix(...@@ -2346,6 +2353,7 @@ fn netBindIpPosix(
2346 address: *const IpAddress,2353 address: *const IpAddress,
2347 options: IpAddress.BindOptions,2354 options: IpAddress.BindOptions,
2348) IpAddress.BindError!net.Socket {2355) IpAddress.BindError!net.Socket {
2356 if (!have_networking) return error.NetworkDown;
2349 const t: *Threaded = @ptrCast(@alignCast(userdata));2357 const t: *Threaded = @ptrCast(@alignCast(userdata));
2350 const family = posixAddressFamily(address);2358 const family = posixAddressFamily(address);
2351 const socket_fd = try openSocketPosix(t, family, options);2359 const socket_fd = try openSocketPosix(t, family, options);
...@@ -2421,9 +2429,6 @@ fn openSocketPosix(...@@ -2421,9 +2429,6 @@ fn openSocketPosix(
2421 return socket_fd;2429 return socket_fd;
2422}2430}
24232431
2424const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩
2425const have_accept4 = !socket_flags_unsupported;
2426
2427fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream {2432fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream {
2428 const t: *Threaded = @ptrCast(@alignCast(userdata));2433 const t: *Threaded = @ptrCast(@alignCast(userdata));
2429 var storage: PosixAddress = undefined;2434 var storage: PosixAddress = undefined;
...@@ -2534,14 +2539,13 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net....@@ -2534,14 +2539,13 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.
2534 }2539 }
2535}2540}
25362541
2537const have_sendmmsg = builtin.os.tag == .linux;
2538
2539fn netSend(2542fn netSend(
2540 userdata: ?*anyopaque,2543 userdata: ?*anyopaque,
2541 handle: net.Socket.Handle,2544 handle: net.Socket.Handle,
2542 messages: []net.OutgoingMessage,2545 messages: []net.OutgoingMessage,
2543 flags: net.SendFlags,2546 flags: net.SendFlags,
2544) struct { ?net.Socket.SendError, usize } {2547) struct { ?net.Socket.SendError, usize } {
2548 if (!have_networking) return .{ error.NetworkDown, 0 };
2545 const t: *Threaded = @ptrCast(@alignCast(userdata));2549 const t: *Threaded = @ptrCast(@alignCast(userdata));
25462550
2547 const posix_flags: u32 =2551 const posix_flags: u32 =
...@@ -2703,7 +2707,7 @@ fn netSendMany(...@@ -2703,7 +2707,7 @@ fn netSendMany(
2703 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type.2707 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type.
2704 .PIPE => return error.SocketUnconnected,2708 .PIPE => return error.SocketUnconnected,
2705 .AFNOSUPPORT => return error.AddressFamilyUnsupported,2709 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
2706 .HOSTUNREACH => return error.NetworkUnreachable,2710 .HOSTUNREACH => return error.HostUnreachable,
2707 .NETUNREACH => return error.NetworkUnreachable,2711 .NETUNREACH => return error.NetworkUnreachable,
2708 .NOTCONN => return error.SocketUnconnected,2712 .NOTCONN => return error.SocketUnconnected,
2709 .NETDOWN => return error.NetworkDown,2713 .NETDOWN => return error.NetworkDown,
...@@ -2720,6 +2724,7 @@ fn netReceive(...@@ -2720,6 +2724,7 @@ fn netReceive(
2720 flags: net.ReceiveFlags,2724 flags: net.ReceiveFlags,
2721 timeout: Io.Timeout,2725 timeout: Io.Timeout,
2722) struct { ?net.Socket.ReceiveTimeoutError, usize } {2726) struct { ?net.Socket.ReceiveTimeoutError, usize } {
2727 if (!have_networking) return .{ error.NetworkDown, 0 };
2723 const t: *Threaded = @ptrCast(@alignCast(userdata));2728 const t: *Threaded = @ptrCast(@alignCast(userdata));
27242729
2725 // recvmmsg is useless, here's why:2730 // recvmmsg is useless, here's why:
...@@ -2847,8 +2852,8 @@ fn netWritePosix(...@@ -2847,8 +2852,8 @@ fn netWritePosix(
2847 data: []const []const u8,2852 data: []const []const u8,
2848 splat: usize,2853 splat: usize,
2849) net.Stream.Writer.Error!usize {2854) net.Stream.Writer.Error!usize {
2855 if (!have_networking) return error.NetworkDown;
2850 const t: *Threaded = @ptrCast(@alignCast(userdata));2856 const t: *Threaded = @ptrCast(@alignCast(userdata));
2851 try t.checkCancel();
28522857
2853 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;2858 var iovecs: [max_iovecs_len]posix.iovec_const = undefined;
2854 var msg: posix.msghdr_const = .{2859 var msg: posix.msghdr_const = .{
...@@ -2889,7 +2894,37 @@ fn netWritePosix(...@@ -2889,7 +2894,37 @@ fn netWritePosix(
2889 },2894 },
2890 };2895 };
2891 const flags = posix.MSG.NOSIGNAL;2896 const flags = posix.MSG.NOSIGNAL;
2892 return posix.sendmsg(fd, &msg, flags);2897 while (true) {
2898 try t.checkCancel();
2899 const rc = posix.system.sendmsg(fd, &msg, flags);
2900 switch (posix.errno(rc)) {
2901 .SUCCESS => return @intCast(rc),
2902 .INTR => continue,
2903 .CANCELED => return error.Canceled,
2904
2905 .ACCES => |err| return errnoBug(err),
2906 .AGAIN => |err| return errnoBug(err),
2907 .ALREADY => return error.FastOpenAlreadyInProgress,
2908 .BADF => |err| return errnoBug(err), // always a race condition
2909 .CONNRESET => return error.ConnectionResetByPeer,
2910 .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set.
2911 .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument.
2912 .INVAL => |err| return errnoBug(err), // Invalid argument passed.
2913 .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified
2914 .MSGSIZE => |err| return errnoBug(err),
2915 .NOBUFS => return error.SystemResources,
2916 .NOMEM => return error.SystemResources,
2917 .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket.
2918 .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type.
2919 .PIPE => return error.SocketUnconnected,
2920 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
2921 .HOSTUNREACH => return error.HostUnreachable,
2922 .NETUNREACH => return error.NetworkUnreachable,
2923 .NOTCONN => return error.SocketUnconnected,
2924 .NETDOWN => return error.NetworkDown,
2925 else => |err| return posix.unexpectedErrno(err),
2926 }
2927 }
2893}2928}
28942929
2895fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {2930fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void {
...@@ -2913,6 +2948,7 @@ fn netInterfaceNameResolve(...@@ -2913,6 +2948,7 @@ fn netInterfaceNameResolve(
2913 userdata: ?*anyopaque,2948 userdata: ?*anyopaque,
2914 name: *const net.Interface.Name,2949 name: *const net.Interface.Name,
2915) net.Interface.Name.ResolveError!net.Interface {2950) net.Interface.Name.ResolveError!net.Interface {
2951 if (!have_networking) return error.InterfaceNotFound;
2916 const t: *Threaded = @ptrCast(@alignCast(userdata));2952 const t: *Threaded = @ptrCast(@alignCast(userdata));
29172953
2918 if (native_os == .linux) {2954 if (native_os == .linux) {
lib/std/Io/net.zig+26-7
...@@ -309,6 +309,7 @@ pub const IpAddress = union(enum) {...@@ -309,6 +309,7 @@ pub const IpAddress = union(enum) {
309 AccessDenied,309 AccessDenied,
310 /// Non-blocking was requested and the operation cannot return immediately.310 /// Non-blocking was requested and the operation cannot return immediately.
311 WouldBlock,311 WouldBlock,
312 NetworkDown,
312 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;313 } || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
313314
314 pub const ConnectOptions = struct {315 pub const ConnectOptions = struct {
...@@ -1062,7 +1063,7 @@ pub const Socket = struct {...@@ -1062,7 +1063,7 @@ pub const Socket = struct {
1062 AddressFamilyUnsupported,1063 AddressFamilyUnsupported,
1063 /// Another TCP Fast Open is already in progress.1064 /// Another TCP Fast Open is already in progress.
1064 FastOpenAlreadyInProgress,1065 FastOpenAlreadyInProgress,
1065 /// Network connection was unexpectedly closed by recipient.1066 /// Network session was unexpectedly closed by recipient.
1066 ConnectionResetByPeer,1067 ConnectionResetByPeer,
1067 /// Local end has been shut down on a connection-oriented socket, or1068 /// Local end has been shut down on a connection-oriented socket, or
1068 /// the socket was never connected.1069 /// the socket was never connected.
...@@ -1242,15 +1243,33 @@ pub const Stream = struct {...@@ -1242,15 +1243,33 @@ pub const Stream = struct {
1242 stream: Stream,1243 stream: Stream,
1243 err: ?Error = null,1244 err: ?Error = null,
12441245
1245 pub const Error = std.posix.SendMsgError || error{1246 pub const Error = error{
1247 /// Another TCP Fast Open is already in progress.
1248 FastOpenAlreadyInProgress,
1249 /// Network session was unexpectedly closed by recipient.
1246 ConnectionResetByPeer,1250 ConnectionResetByPeer,
1247 SocketNotBound,1251 /// The output queue for a network interface was full. This generally indicates that the
1248 MessageOversize,1252 /// interface has stopped sending, but may be caused by transient congestion. (Normally,
1249 NetworkDown,1253 /// this does not occur in Linux. Packets are just silently dropped when a device queue
1254 /// overflows.)
1255 ///
1256 /// This is also caused when there is not enough kernel memory available.
1250 SystemResources,1257 SystemResources,
1258 /// No route to network.
1259 NetworkUnreachable,
1260 /// Network reached but no route to host.
1261 HostUnreachable,
1262 /// The local network interface used to reach the destination is down.
1263 NetworkDown,
1264 /// The destination address is not listening.
1265 ConnectionRefused,
1266 /// The passed address didn't have the correct address family in its sa_family field.
1267 AddressFamilyUnsupported,
1268 /// Local end has been shut down on a connection-oriented socket, or
1269 /// the socket was never connected.
1251 SocketUnconnected,1270 SocketUnconnected,
1252 Unexpected,1271 SocketNotBound,
1253 } || Io.Cancelable;1272 } || Io.UnexpectedError || Io.Cancelable;
12541273
1255 pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {1274 pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {
1256 return .{1275 return .{