diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 4a8e0059e9..292e421bb1 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -337,11 +337,21 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { return self.pop(); } - // For a nicer API, `items.len` is the length, not the capacity. - // This requires "unsafe" slicing. - fn allocatedSlice(self: Self) Slice { + /// Returns a slice of all the items plus the extra capacity, whose memory + /// contents are undefined. + pub fn allocatedSlice(self: Self) Slice { + // For a nicer API, `items.len` is the length, not the capacity. + // This requires "unsafe" slicing. return self.items.ptr[0..self.capacity]; } + + /// Returns a slice of only the extra capacity after items. + /// This can be useful for writing directly into an `ArrayList`. + /// Note that such an operation must be followed up with a direct + /// modification of `self.items.len`. + pub fn unusedCapacitySlice(self: Self) Slice { + return self.allocatedSlice()[self.items.len..]; + } }; } diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index b1dcc3737f..acab303f73 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -186,6 +186,56 @@ pub const ChildProcess = struct { pub const exec2 = @compileError("deprecated: exec2 is renamed to exec"); + fn collectOutputPosix( + child: *const ChildProcess, + stdout: *std.ArrayList(u8), + stderr: *std.ArrayList(u8), + max_output_bytes: usize, + ) !void { + var poll_fds = [_]os.pollfd{ + .{ .fd = child.stdout.?.handle, .events = os.POLLIN, .revents = undefined }, + .{ .fd = child.stderr.?.handle, .events = os.POLLIN, .revents = undefined }, + }; + + var dead_fds: usize = 0; + // We ask for ensureCapacity with this much extra space. This has more of an + // effect on small reads because once the reads start to get larger the amount + // of space an ArrayList will allocate grows exponentially. + const bump_amt = 512; + + while (dead_fds < poll_fds.len) { + const events = try os.poll(&poll_fds, std.math.maxInt(i32)); + if (events == 0) continue; + + // Try reading whatever is available before checking the error + // conditions. + if (poll_fds[0].revents & os.POLLIN != 0) { + // stdout is ready. + const new_capacity = std.math.min(stdout.items.len + bump_amt, max_output_bytes); + if (new_capacity == stdout.capacity) return error.StdoutStreamTooLong; + try stdout.ensureCapacity(new_capacity); + stdout.items.len += try os.read(poll_fds[0].fd, stdout.unusedCapacitySlice()); + } + if (poll_fds[1].revents & os.POLLIN != 0) { + // stderr is ready. + const new_capacity = std.math.min(stderr.items.len + bump_amt, max_output_bytes); + if (new_capacity == stderr.capacity) return error.StderrStreamTooLong; + try stderr.ensureCapacity(new_capacity); + stderr.items.len += try os.read(poll_fds[1].fd, stderr.unusedCapacitySlice()); + } + + // Exclude the fds that signaled an error. + if (poll_fds[0].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) { + poll_fds[0].fd = -1; + dead_fds += 1; + } + if (poll_fds[1].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) { + poll_fds[1].fd = -1; + dead_fds += 1; + } + } + } + /// Spawns a child process, waits for it, collecting stdout and stderr, and then returns. /// If it succeeds, the caller owns result.stdout and result.stderr memory. pub fn exec(args: struct { @@ -210,19 +260,33 @@ pub const ChildProcess = struct { try child.spawn(); - const stdout_in = child.stdout.?.reader(); - const stderr_in = child.stderr.?.reader(); + // TODO collect output in a deadlock-avoiding way on Windows. + // https://github.com/ziglang/zig/issues/6343 + if (builtin.os.tag == .windows) { + const stdout_in = child.stdout.?.reader(); + const stderr_in = child.stderr.?.reader(); - // TODO https://github.com/ziglang/zig/issues/6343 - const stdout = try stdout_in.readAllAlloc(args.allocator, args.max_output_bytes); - errdefer args.allocator.free(stdout); - const stderr = try stderr_in.readAllAlloc(args.allocator, args.max_output_bytes); - errdefer args.allocator.free(stderr); + const stdout = try stdout_in.readAllAlloc(args.allocator, args.max_output_bytes); + errdefer args.allocator.free(stdout); + const stderr = try stderr_in.readAllAlloc(args.allocator, args.max_output_bytes); + errdefer args.allocator.free(stderr); + + return ExecResult{ + .term = try child.wait(), + .stdout = stdout, + .stderr = stderr, + }; + } + + var stdout = std.ArrayList(u8).init(args.allocator); + var stderr = std.ArrayList(u8).init(args.allocator); + + try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes); return ExecResult{ .term = try child.wait(), - .stdout = stdout, - .stderr = stderr, + .stdout = stdout.toOwnedSlice(), + .stderr = stderr.toOwnedSlice(), }; } diff --git a/lib/std/os.zig b/lib/std/os.zig index 28d4f6bb1a..66f65b5b5d 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -5269,7 +5269,8 @@ pub const PollError = error{ pub fn poll(fds: []pollfd, timeout: i32) PollError!usize { while (true) { - const rc = system.poll(fds.ptr, fds.len, timeout); + const fds_count = math.cast(nfds_t, fds.len) catch return error.SystemResources; + const rc = system.poll(fds.ptr, fds_count, timeout); if (builtin.os.tag == .windows) { if (rc == windows.ws2_32.SOCKET_ERROR) { switch (windows.ws2_32.WSAGetLastError()) { diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig index b9fe23eeb1..5016134e76 100644 --- a/lib/std/os/bits/darwin.zig +++ b/lib/std/os/bits/darwin.zig @@ -1461,7 +1461,7 @@ pub const LOCK_EX = 2; pub const LOCK_UN = 8; pub const LOCK_NB = 4; -pub const nfds_t = usize; +pub const nfds_t = u32; pub const pollfd = extern struct { fd: fd_t, events: i16, diff --git a/lib/std/os/bits/freebsd.zig b/lib/std/os/bits/freebsd.zig index 08713bb2e4..13e14df33c 100644 --- a/lib/std/os/bits/freebsd.zig +++ b/lib/std/os/bits/freebsd.zig @@ -1480,3 +1480,37 @@ pub const rlimit = extern struct { pub const SHUT_RD = 0; pub const SHUT_WR = 1; pub const SHUT_RDWR = 2; + +pub const nfds_t = u32; + +pub const pollfd = extern struct { + fd: fd_t, + events: i16, + revents: i16, +}; + +/// any readable data available. +pub const POLLIN = 0x0001; +/// OOB/Urgent readable data. +pub const POLLPRI = 0x0002; +/// file descriptor is writeable. +pub const POLLOUT = 0x0004; +/// non-OOB/URG data available. +pub const POLLRDNORM = 0x0040; +/// no write type differentiation. +pub const POLLWRNORM = POLLOUT; +/// OOB/Urgent readable data. +pub const POLLRDBAND = 0x0080; +/// OOB/Urgent data can be written. +pub const POLLWRBAND = 0x0100; +/// like POLLIN, except ignore EOF. +pub const POLLINIGNEOF = 0x2000; +/// some poll error occurred. +pub const POLLERR = 0x0008; +/// file descriptor was "hung up". +pub const POLLHUP = 0x0010; +/// requested events "invalid". +pub const POLLNVAL = 0x0020; + +pub const POLLSTANDARD = POLLIN | POLLPRI | POLLOUT | POLLRDNORM | POLLRDBAND | + POLLWRBAND | POLLERR | POLLHUP | POLLNVAL;