commit 23d25dbb9e4e469eb2e59d50bd888b7a61ffb876 (tree)
parent 642f329ac91d69d02588ba15714edafb09e709da
Author: Andrew Kelley <andrew@ziglang.org>
Date: Fri, 9 Jan 2026 19:21:59 -0800
std.process.Child.collectOutput: change back to other impl
this one avoids calling poll() more than necessary
Diffstat:
2 files changed, 43 insertions(+), 45 deletions(-)
diff --git a/lib/std/Io.zig b/lib/std/Io.zig
@@ -260,7 +260,7 @@ pub const Operation = union(enum) {
file_read_streaming: FileReadStreaming,
pub const Noop = struct {
- reserved: [2]usize,
+ reserved: [2]usize = .{ 0, 0 },
status: Status(void) = .{ .result = {} },
};
diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig
@@ -137,7 +137,6 @@ pub const CollectOutputOptions = struct {
allocator: ?Allocator = null,
stdout_limit: Io.Limit = .unlimited,
stderr_limit: Io.Limit = .unlimited,
- timeout: Io.Timeout = .none,
};
/// Collect the output from the process's stdout and stderr. Will return once
@@ -147,55 +146,54 @@ pub const CollectOutputOptions = struct {
/// The process must have been started with stdout and stderr set to
/// `process.SpawnOptions.StdIo.pipe`.
pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void {
+ const files: [2]Io.File = .{ child.stdout.?, child.stderr.? };
const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr };
const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit };
-
- if (options.allocator) |gpa| {
- for (lists) |list| try list.ensureUnusedCapacity(gpa, 1);
- } else {
- for (lists) |list| {
- if (list.unusedCapacitySlice().len == 0)
- return error.StreamTooLong;
- }
- }
-
+ var dones: [2]bool = .{ false, false };
+ var reads: [2]Io.Operation = undefined;
var vecs: [2][1][]u8 = undefined;
- for (lists, &vecs) |list, *vec|
- vec[0] = list.unusedCapacitySlice();
-
- var operations: [2]Io.Operation = .{
- .{ .file_read_streaming = .{
- .file = child.stdout.?,
- .data = &vecs[0],
- } },
- .{ .file_read_streaming = .{
- .file = child.stderr.?,
- .data = &vecs[1],
- } },
- };
-
- var batch: Io.Batch = .init(&operations);
- batch.submit(io);
- defer batch.cancel(io);
-
- var pending = operations.len;
- var retry_index: ?usize = null;
- while (pending > 0) {
- const resubmissions: []const usize = if (retry_index) |i| &.{i} else &.{};
- const index = try batch.wait(io, resubmissions, options.timeout);
- const n = try operations[index].file_read_streaming.status.result;
- if (n == 0) {
- pending -= 1;
- } else {
- retry_index = index;
- const list = lists[index];
- const limit = limits[index];
- list.items.len += n;
- if (list.items.len >= @intFromEnum(limit)) return error.StreamTooLong;
+ while (true) {
+ for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| {
+ if (done) {
+ read.* = .{ .noop = .{} };
+ continue;
+ }
if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1);
const cap = list.unusedCapacitySlice();
if (cap.len == 0) return error.StreamTooLong;
- vecs[index][0] = cap;
+ vec[0] = cap;
+ read.* = .{ .file_read_streaming = .{
+ .file = file,
+ .data = vec,
+ } };
+ }
+ var all_done = true;
+ var any_canceled = false;
+ var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {};
+ try io.vtable.batch(io.userdata, &reads);
+ for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| {
+ if (done.*) continue;
+ const n = read.file_read_streaming.status.result catch |err| switch (err) {
+ error.Canceled => {
+ any_canceled = true;
+ continue;
+ },
+ error.WouldBlock => continue,
+ else => |e| {
+ other_err = e;
+ continue;
+ },
+ };
+ if (n == 0) {
+ done.* = true;
+ } else {
+ all_done = false;
+ }
+ list.items.len += n;
+ if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong;
}
+ if (any_canceled) return error.Canceled;
+ try other_err;
+ if (all_done) return;
}
}