Merge branch 'LemonBoy-cprocess'

This is a partial merge of #6750. I took the Posix code paths and
dropped the Windows code paths, and then did the improvements noted in
the comments.

The Windows implementation is still TODO.
This commit is contained in:
Andrew Kelley 2020-12-29 11:16:28 -07:00
commit e54fd25781
5 changed files with 123 additions and 14 deletions

View File

@ -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..];
}
};
}

View File

@ -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(),
};
}

View File

@ -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()) {

View File

@ -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,

View File

@ -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;