From 23d25dbb9e4e469eb2e59d50bd888b7a61ffb876 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 9 Jan 2026 19:21:59 -0800 Subject: [PATCH] std.process.Child.collectOutput: change back to other impl this one avoids calling poll() more than necessary --- lib/std/Io.zig | 2 +- lib/std/process/Child.zig | 86 +++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index d916bb6995..2f34dc07e4 100644 --- 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 index 9d31b77080..364c52446f 100644 --- 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; } }