| ... | @@ -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 | }; |
| 142 | | 141 | |
| 143 | /// Collect the output from the process's stdout and stderr. Will return once | 142 | /// 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 to | 146 | /// The process must have been started with stdout and stderr set to |
| 148 | /// `process.SpawnOptions.StdIo.pipe`. | 147 | /// `process.SpawnOptions.StdIo.pipe`. |
| 149 | pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { | 148 | pub 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 }; |
| 152 | | 152 | 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| { |
| 165 | | 157 | 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 | } |