authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-01 23:43:12-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 00:27:08-08:00
logf2cf7b538f5c8ec0da69715d39ad6d433f3168d6
tree4595de50be427a3503ee0d3f9505ea77fc1104eb
parent0ca83dd9d2d3fb38bc1e7baf773fb67a37a2bd2a

std.Io.Threaded: fix the child process error fd mechanism


1 files changed, 46 insertions(+), 22 deletions(-)

lib/std/Io/Threaded.zig+46-22
...@@ -12790,9 +12790,15 @@ fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions)...@@ -12790,9 +12790,15 @@ fn processSpawnUnsupported(userdata: ?*anyopaque, options: process.SpawnOptions)
12790 return error.Unexpected;12790 return error.Unexpected;
12791}12791}
1279212792
12793fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {12793const Spawned = struct {
12794 const t: *Threaded = @ptrCast(@alignCast(userdata));12794 pid: posix.pid_t,
12795 err_fd: posix.fd_t,
12796 stdin: ?File,
12797 stdout: ?File,
12798 stderr: ?File,
12799};
1279512800
12801fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Spawned {
12796 // The child process does need to access (one end of) these pipes. However,12802 // The child process does need to access (one end of) these pipes. However,
12797 // we must initially set CLOEXEC to avoid a race condition. If another thread12803 // we must initially set CLOEXEC to avoid a race condition. If another thread
12798 // is racing to spawn a different child process, we don't want it to inherit12804 // is racing to spawn a different child process, we don't want it to inherit
...@@ -12947,9 +12953,9 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce...@@ -12947,9 +12953,9 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
12947 }12953 }
1294812954
12949 const pid: posix.pid_t = @intCast(pid_result); // We are the parent.12955 const pid: posix.pid_t = @intCast(pid_result); // We are the parent.
12956 errdefer comptime unreachable; // The child is forked; we must not error from now on
1295012957
12951 posix.close(err_pipe[1]); // make sure only the child holds the write end open12958 posix.close(err_pipe[1]); // make sure only the child holds the write end open
12952 defer posix.close(err_pipe[0]);
1295312959
12954 if (options.stdin == .pipe) posix.close(stdin_pipe[0]);12960 if (options.stdin == .pipe) posix.close(stdin_pipe[0]);
12955 if (options.stdout == .pipe) posix.close(stdout_pipe[1]);12961 if (options.stdout == .pipe) posix.close(stdout_pipe[1]);
...@@ -12959,8 +12965,31 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce...@@ -12959,8 +12965,31 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
1295912965
12960 options.progress_node.setIpcFd(prog_pipe[0]);12966 options.progress_node.setIpcFd(prog_pipe[0]);
1296112967
12968 return .{
12969 .pid = pid,
12970 .err_fd = err_pipe[0],
12971 .stdin = switch (options.stdin) {
12972 .pipe => .{ .handle = stdin_pipe[1] },
12973 else => null,
12974 },
12975 .stdout = switch (options.stdout) {
12976 .pipe => .{ .handle = stdout_pipe[0] },
12977 else => null,
12978 },
12979 .stderr = switch (options.stderr) {
12980 .pipe => .{ .handle = stderr_pipe[0] },
12981 else => null,
12982 },
12983 };
12984}
12985
12986fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
12987 const t: *Threaded = @ptrCast(@alignCast(userdata));
12988 const spawned = try spawnPosix(t, options);
12989 defer posix.close(spawned.err_fd);
12990
12962 // Wait for the child to report any errors in or before `execvpe`.12991 // Wait for the child to report any errors in or before `execvpe`.
12963 if (readIntFd(t, err_pipe[0])) |child_err_int| {12992 if (readIntFd(t, spawned.err_fd)) |child_err_int| {
12964 const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int));12993 const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int));
12965 return child_err;12994 return child_err;
12966 } else |read_err| switch (read_err) {12995 } else |read_err| switch (read_err) {
...@@ -12976,20 +13005,11 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce...@@ -12976,20 +13005,11 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
12976 }13005 }
1297713006
12978 return .{13007 return .{
12979 .id = pid,13008 .id = spawned.pid,
12980 .thread_handle = {},13009 .thread_handle = {},
12981 .stdin = switch (options.stdin) {13010 .stdin = spawned.stdin,
12982 .pipe => .{ .handle = stdin_pipe[1] },13011 .stdout = spawned.stdout,
12983 else => null,13012 .stderr = spawned.stderr,
12984 },
12985 .stdout = switch (options.stdout) {
12986 .pipe => .{ .handle = stdout_pipe[0] },
12987 else => null,
12988 },
12989 .stderr = switch (options.stderr) {
12990 .pipe => .{ .handle = stderr_pipe[0] },
12991 else => null,
12992 },
12993 .request_resource_usage_statistics = options.request_resource_usage_statistics,13013 .request_resource_usage_statistics = options.request_resource_usage_statistics,
12994 };13014 };
12995}13015}
...@@ -13156,10 +13176,13 @@ fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn {...@@ -13156,10 +13176,13 @@ fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn {
13156 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,13176 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
13157 // "Why'd you have to go and make things so complicated?"13177 // "Why'd you have to go and make things so complicated?"
13158 if (builtin.link_libc) {13178 if (builtin.link_libc) {
13159 // The _exit(2) function does nothing but make the exit syscall, unlike exit(3)13179 // The `_exit` function does nothing but make the exit syscall, unlike `exit`.
13160 std.c._exit(1);13180 std.c._exit(1);
13181 } else if (native_os == .linux and !builtin.single_threaded) {
13182 std.os.linux.exit_group(1);
13183 } else {
13184 posix.system.exit(1);
13161 }13185 }
13162 posix.system.exit(1);
13163}13186}
1316413187
13165fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void {13188fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void {
...@@ -13190,7 +13213,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt {...@@ -13190,7 +13213,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt {
13190 switch (posix.errno(rc)) {13213 switch (posix.errno(rc)) {
13191 .SUCCESS => {13214 .SUCCESS => {
13192 const n: usize = @intCast(rc);13215 const n: usize = @intCast(rc);
13193 if (n == 0) return error.EndOfStream;13216 if (n == 0) break;
13194 i += n;13217 i += n;
13195 continue;13218 continue;
13196 },13219 },
...@@ -13198,6 +13221,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt {...@@ -13198,6 +13221,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt {
13198 else => |err| return posix.unexpectedErrno(err),13221 else => |err| return posix.unexpectedErrno(err),
13199 }13222 }
13200 }13223 }
13224 if (buffer.len - i != 0) return error.EndOfStream;
13201 return @intCast(std.mem.readInt(u64, &buffer, .little));13225 return @intCast(std.mem.readInt(u64, &buffer, .little));
13202}13226}
1320313227
...@@ -14371,9 +14395,9 @@ pub fn execvpeZ(...@@ -14371,9 +14395,9 @@ pub fn execvpeZ(
14371 file: [*:0]const u8,14395 file: [*:0]const u8,
14372 argv_ptr: [*:null]const ?[*:0]const u8,14396 argv_ptr: [*:null]const ?[*:0]const u8,
14373 envp: [*:null]const ?[*:0]const u8,14397 envp: [*:null]const ?[*:0]const u8,
14374 optional_PATH: ?[]const u8,14398 PATH: []const u8,
14375) process.ReplaceError {14399) process.ReplaceError {
14376 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, optional_PATH);14400 return execvpeZ_expandArg0(.no_expand, file, argv_ptr, envp, PATH);
14377}14401}
1437814402
14379fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {14403fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {