diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 37fc2ca023..0dd4b93280 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -539,6 +539,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); var result: ?Path = null; + var eos_err: error{EndOfStream}!void = {}; const stdout = zp.multi_reader.fileReader(0); @@ -549,7 +550,13 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. error.ReadFailed => return stdout.err.?, }; const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { - error.EndOfStream => |e| return e, + error.EndOfStream => |e| { + // Better to report the crash with stderr below, but we set + // this in case the child exits successfully while violating + // this protocol. + eos_err = e; + break; + }, error.ReadFailed => return stdout.err.?, }; switch (header.tag) { @@ -647,6 +654,8 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); } + try eos_err; + return result; } diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index a1ea42a7d8..0cfa777e96 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -246,6 +246,14 @@ pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillE if (!any_completed) return error.EndOfStream; } +/// Wait until all streams fail or reach the end. +pub fn fillRemaining(mr: *MultiReader, timeout: Io.Timeout) Io.Batch.WaitError!void { + while (fill(mr, 1, timeout)) |_| {} else |err| switch (err) { + error.EndOfStream => return, + else => |e| return e, + } +} + fn rebaseGrowing(mr: *MultiReader, context: *Context, capacity: usize) Allocator.Error!void { const gpa = mr.gpa; const r = &context.fr.interface; diff --git a/lib/std/process.zig b/lib/std/process.zig index b5de41f5d8..6f3c155f6d 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -488,6 +488,7 @@ pub const RunOptions = struct { create_no_window: bool = true, /// Darwin-only. Disable ASLR for the child process. disable_aslr: bool = false, + timeout: Io.Timeout = .none, }; pub const RunResult = struct { @@ -529,6 +530,7 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { .stderr = &stderr, .stdout_limit = options.stdout_limit, .stderr_limit = options.stderr_limit, + .timeout = options.timeout, }); const term = try child.wait(io); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 19c974ff9f..fe6dfa389d 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -137,6 +137,7 @@ 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 @@ -173,7 +174,7 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) remaining += 1; } while (remaining > 0) { - try batch.wait(io, .none); + try batch.wait(io, options.timeout); while (batch.next()) |op| { const n = try reads[op].file_read_streaming.status.result; if (n == 0) { diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 02b3df54dc..6a3f4b4813 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -268,7 +268,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar }); const run_res = std.process.run(gpa, io, .{ - .max_output_bytes = 1024 * 1024, + .stdout_limit = .limited(1024 * 1024), + .stderr_limit = .limited(1024 * 1024), .argv = argv.items, .environ_map = &environ_map, // Some C compilers, such as Clang, are known to rely on argv[0] to find the path @@ -584,7 +585,8 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 { try argv.append(arg1); const run_res = std.process.run(gpa, io, .{ - .max_output_bytes = 1024 * 1024, + .stdout_limit = .limited(1024 * 1024), + .stderr_limit = .limited(1024 * 1024), .argv = argv.items, .environ_map = &environ_map, // Some C compilers, such as Clang, are known to rely on argv[0] to find the path diff --git a/src/Compilation.zig b/src/Compilation.zig index 98c1b56e38..6b6021ab3c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -6873,6 +6873,7 @@ fn spawnZigRc( child_progress_node: std.Progress.Node, ) !void { const io = comp.io; + const gpa = comp.gpa; var node_name: std.ArrayList(u8) = .empty; defer node_name.deinit(arena); @@ -6887,55 +6888,69 @@ fn spawnZigRc( }); defer child.kill(io); - var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); + defer multi_reader.deinit(); - const stdout = poller.reader(.stdout); + const stdout = multi_reader.fileReader(0); + const MessageHeader = std.zig.Server.Message.Header; - poll: while (true) { - const MessageHeader = std.zig.Server.Message.Header; - while (stdout.buffered().len < @sizeOf(MessageHeader)) if (!try poller.poll()) break :poll; - const header = stdout.takeStruct(MessageHeader, .little) catch unreachable; - while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; - const body = stdout.take(header.bytes_len) catch unreachable; + var eos_err: error{EndOfStream}!void = {}; + while (true) { + const header = stdout.interface.takeStruct(MessageHeader, .little) catch |err| switch (err) { + error.EndOfStream => break, + error.ReadFailed => return stdout.err.?, + }; + const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { + error.EndOfStream => |e| { + // Better to report the crash with stderr below, but we set + // this in case the child exits successfully while violating + // this protocol. + eos_err = e; + break; + }, + error.ReadFailed => return stdout.err.?, + }; switch (header.tag) { // We expect exactly one ErrorBundle, and if any error_bundle header is // sent then it's a fatal error. .error_bundle => { - const error_bundle = try std.zig.Server.allocErrorBundle(comp.gpa, body); + const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle); }, else => {}, // ignore other messages } } - // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) - const stderr = poller.reader(.stderr); + try multi_reader.fillRemaining(.none); + // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) const term = child.wait(io) catch |err| { return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {t}", .{ argv[0], err }); }; + const stderr = multi_reader.reader(1).buffered(); + switch (term) { .exited => |code| { if (code != 0) { - log.err("zig rc failed with stderr:\n{s}", .{stderr.buffered()}); + log.err("zig rc failed with stderr:\n{s}", .{stderr}); return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code}); } }, .signal => |sig| { - log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr.buffered() }); + log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr }); return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); }, else => { - log.err("zig rc terminated with stderr:\n{s}", .{stderr.buffered()}); + log.err("zig rc terminated with stderr:\n{s}", .{stderr}); return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); }, } + + try eos_err; } pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {