authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-09 19:21:59-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 12:10:01-08:00
log72894900ec27b4af48d23a4c4ea6579e45300cc1
treeb96faac7fe55c7061518dd6394802a9b746a9d7e
parent45bc4b4e36bc34a2e246e4433a21030ee961fe71

std.process.Child.collectOutput: change back to other impl

this one avoids calling poll() more than necessary

2 files changed, 43 insertions(+), 45 deletions(-)

lib/std/Io.zig+1-1
...@@ -260,7 +260,7 @@ pub const Operation = union(enum) {...@@ -260,7 +260,7 @@ pub const Operation = union(enum) {
260 file_read_streaming: FileReadStreaming,260 file_read_streaming: FileReadStreaming,
261261
262 pub const Noop = struct {262 pub const Noop = struct {
263 reserved: [2]usize,263 reserved: [2]usize = .{ 0, 0 },
264 status: Status(void) = .{ .result = {} },264 status: Status(void) = .{ .result = {} },
265 };265 };
266266
lib/std/process/Child.zig+42-44
...@@ -137,7 +137,6 @@ pub const CollectOutputOptions = struct {...@@ -137,7 +137,6 @@ pub const CollectOutputOptions = struct {
137 allocator: ?Allocator = null,137 allocator: ?Allocator = null,
138 stdout_limit: Io.Limit = .unlimited,138 stdout_limit: Io.Limit = .unlimited,
139 stderr_limit: Io.Limit = .unlimited,139 stderr_limit: Io.Limit = .unlimited,
140 timeout: Io.Timeout = .none,
141};140};
142141
143/// Collect the output from the process's stdout and stderr. Will return once142/// Collect the output from the process's stdout and stderr. Will return once
...@@ -147,55 +146,54 @@ pub const CollectOutputOptions = struct {...@@ -147,55 +146,54 @@ pub const CollectOutputOptions = struct {
147/// The process must have been started with stdout and stderr set to146/// The process must have been started with stdout and stderr set to
148/// `process.SpawnOptions.StdIo.pipe`.147/// `process.SpawnOptions.StdIo.pipe`.
149pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void {148pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void {
149 const files: [2]Io.File = .{ child.stdout.?, child.stderr.? };
150 const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr };150 const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr };
151 const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit };151 const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit };
152152 var dones: [2]bool = .{ false, false };
153 if (options.allocator) |gpa| {153 var reads: [2]Io.Operation = undefined;
154 for (lists) |list| try list.ensureUnusedCapacity(gpa, 1);
155 } else {
156 for (lists) |list| {
157 if (list.unusedCapacitySlice().len == 0)
158 return error.StreamTooLong;
159 }
160 }
161
162 var vecs: [2][1][]u8 = undefined;154 var vecs: [2][1][]u8 = undefined;
163 for (lists, &vecs) |list, *vec|155 while (true) {
164 vec[0] = list.unusedCapacitySlice();156 for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| {
165157 if (done) {
166 var operations: [2]Io.Operation = .{158 read.* = .{ .noop = .{} };
167 .{ .file_read_streaming = .{159 continue;
168 .file = child.stdout.?,160 }
169 .data = &vecs[0],
170 } },
171 .{ .file_read_streaming = .{
172 .file = child.stderr.?,
173 .data = &vecs[1],
174 } },
175 };
176
177 var batch: Io.Batch = .init(&operations);
178 batch.submit(io);
179 defer batch.cancel(io);
180
181 var pending = operations.len;
182 var retry_index: ?usize = null;
183 while (pending > 0) {
184 const resubmissions: []const usize = if (retry_index) |i| &.{i} else &.{};
185 const index = try batch.wait(io, resubmissions, options.timeout);
186 const n = try operations[index].file_read_streaming.status.result;
187 if (n == 0) {
188 pending -= 1;
189 } else {
190 retry_index = index;
191 const list = lists[index];
192 const limit = limits[index];
193 list.items.len += n;
194 if (list.items.len >= @intFromEnum(limit)) return error.StreamTooLong;
195 if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1);161 if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1);
196 const cap = list.unusedCapacitySlice();162 const cap = list.unusedCapacitySlice();
197 if (cap.len == 0) return error.StreamTooLong;163 if (cap.len == 0) return error.StreamTooLong;
198 vecs[index][0] = cap;164 vec[0] = cap;
165 read.* = .{ .file_read_streaming = .{
166 .file = file,
167 .data = vec,
168 } };
169 }
170 var all_done = true;
171 var any_canceled = false;
172 var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {};
173 try io.vtable.batch(io.userdata, &reads);
174 for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| {
175 if (done.*) continue;
176 const n = read.file_read_streaming.status.result catch |err| switch (err) {
177 error.Canceled => {
178 any_canceled = true;
179 continue;
180 },
181 error.WouldBlock => continue,
182 else => |e| {
183 other_err = e;
184 continue;
185 },
186 };
187 if (n == 0) {
188 done.* = true;
189 } else {
190 all_done = false;
191 }
192 list.items.len += n;
193 if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong;
199 }194 }
195 if (any_canceled) return error.Canceled;
196 try other_err;
197 if (all_done) return;
200 }198 }
201}199}