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)
1279012790 return error.Unexpected;
1279112791}
1279212792
12793fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
12794 const t: *Threaded = @ptrCast(@alignCast(userdata));
12793const Spawned = struct {
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 {
1279612802 // The child process does need to access (one end of) these pipes. However,
1279712803 // we must initially set CLOEXEC to avoid a race condition. If another thread
1279812804 // 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
1294712953 }
1294812954
1294912955 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
1295112958 posix.close(err_pipe[1]); // make sure only the child holds the write end open
12952 defer posix.close(err_pipe[0]);
1295312959
1295412960 if (options.stdin == .pipe) posix.close(stdin_pipe[0]);
1295512961 if (options.stdout == .pipe) posix.close(stdout_pipe[1]);
......@@ -12959,8 +12965,31 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
1295912965
1296012966 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
1296212991 // 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| {
1296412993 const child_err: process.SpawnError = @errorCast(@errorFromInt(child_err_int));
1296512994 return child_err;
1296612995 } else |read_err| switch (read_err) {
......@@ -12976,20 +13005,11 @@ fn processSpawnPosix(userdata: ?*anyopaque, options: process.SpawnOptions) proce
1297613005 }
1297713006
1297813007 return .{
12979 .id = pid,
13008 .id = spawned.pid,
1298013009 .thread_handle = {},
12981 .stdin = switch (options.stdin) {
12982 .pipe => .{ .handle = stdin_pipe[1] },
12983 else => null,
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 },
13010 .stdin = spawned.stdin,
13011 .stdout = spawned.stdout,
13012 .stderr = spawned.stderr,
1299313013 .request_resource_usage_statistics = options.request_resource_usage_statistics,
1299413014 };
1299513015}
......@@ -13156,10 +13176,13 @@ fn forkBail(fd: posix.fd_t, err: ForkBailError) noreturn {
1315613176 // it caused a deadlock instead of doing an exit syscall. In the words of Avril Lavigne,
1315713177 // "Why'd you have to go and make things so complicated?"
1315813178 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`.
1316013180 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);
1316113185 }
13162 posix.system.exit(1);
1316313186}
1316413187
1316513188fn writeIntFd(fd: posix.fd_t, value: ErrInt) !void {
......@@ -13190,7 +13213,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt {
1319013213 switch (posix.errno(rc)) {
1319113214 .SUCCESS => {
1319213215 const n: usize = @intCast(rc);
13193 if (n == 0) return error.EndOfStream;
13216 if (n == 0) break;
1319413217 i += n;
1319513218 continue;
1319613219 },
......@@ -13198,6 +13221,7 @@ fn readIntFd(t: *Threaded, fd: posix.fd_t) !ErrInt {
1319813221 else => |err| return posix.unexpectedErrno(err),
1319913222 }
1320013223 }
13224 if (buffer.len - i != 0) return error.EndOfStream;
1320113225 return @intCast(std.mem.readInt(u64, &buffer, .little));
1320213226}
1320313227
......@@ -14371,9 +14395,9 @@ pub fn execvpeZ(
1437114395 file: [*:0]const u8,
1437214396 argv_ptr: [*:null]const ?[*:0]const u8,
1437314397 envp: [*:null]const ?[*:0]const u8,
14374 optional_PATH: ?[]const u8,
14398 PATH: []const u8,
1437514399) 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);
1437714401}
1437814402
1437914403fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {