authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-23 17:01:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-27 20:56:48-07:00
logdf46f5af690889508ebcb07b46c8579acaad06a2
treed6ead93a4987b2a995942e05762ffef87345f33e
parentf07116404ae323efceb57cc48459f62e7a4d6f81

std.Progress: include subtrees from child processes


2 files changed, 140 insertions(+), 26 deletions(-)

lib/std/Progress.zig+123-14
...@@ -83,6 +83,22 @@ pub const Node = struct {...@@ -83,6 +83,22 @@ pub const Node = struct {
83 /// Little endian.83 /// Little endian.
84 estimated_total_count: u32,84 estimated_total_count: u32,
85 name: [max_name_len]u8,85 name: [max_name_len]u8,
86
87 fn getIpcFd(s: Storage) ?posix.fd_t {
88 if (s.estimated_total_count != std.math.maxInt(u32))
89 return null;
90
91 return @bitCast(s.completed_count);
92 }
93
94 fn setIpcFd(s: *Storage, fd: posix.fd_t) void {
95 s.estimated_total_count = std.math.maxInt(u32);
96 s.completed_count = @bitCast(fd);
97 }
98
99 comptime {
100 assert((@sizeOf(Storage) % 4) == 0);
101 }
86 };102 };
87103
88 const Parent = enum(u16) {104 const Parent = enum(u16) {
...@@ -201,6 +217,13 @@ pub const Node = struct {...@@ -201,6 +217,13 @@ pub const Node = struct {
201 }217 }
202 }218 }
203219
220 /// Posix-only. Used by `std.process.Child`.
221 pub fn setIpcFd(node: Node, fd: posix.fd_t) void {
222 const index = node.index.unwrap() orelse return;
223 assert(fd != -1);
224 storageByIndex(index).setIpcFd(fd);
225 }
226
204 fn storageByIndex(index: Node.Index) *Node.Storage {227 fn storageByIndex(index: Node.Index) *Node.Storage {
205 return &global_progress.node_storage[@intFromEnum(index)];228 return &global_progress.node_storage[@intFromEnum(index)];
206 }229 }
...@@ -475,14 +498,8 @@ fn serialize() Serialized {...@@ -475,14 +498,8 @@ fn serialize() Serialized {
475 }498 }
476 }499 }
477500
478 // Now we can analyze our copy of the graph without atomics, reconstructing
479 // children lists which do not exist in the canonical data. These are
480 // needed for tree traversal below.
481 const serialized_node_parents = serialized_node_parents_buffer[0..serialized_len];
482 const serialized_node_storage = serialized_node_storage_buffer[0..serialized_len];
483
484 // Remap parents to point inside serialized arrays.501 // Remap parents to point inside serialized arrays.
485 for (serialized_node_parents) |*parent| {502 for (serialized_node_parents_buffer[0..serialized_len]) |*parent| {
486 parent.* = switch (parent.*) {503 parent.* = switch (parent.*) {
487 .unused => unreachable,504 .unused => unreachable,
488 .none => .none,505 .none => .none,
...@@ -490,15 +507,99 @@ fn serialize() Serialized {...@@ -490,15 +507,99 @@ fn serialize() Serialized {
490 };507 };
491 }508 }
492509
510 // Find nodes which correspond to child processes.
511 var pipe_buf: [4096]u8 align(4) = undefined;
512
513 for (
514 serialized_node_parents_buffer[0..serialized_len],
515 serialized_node_storage_buffer[0..serialized_len],
516 0..,
517 ) |main_parent, *main_storage, main_index| {
518 if (main_parent == .unused) continue;
519 const fd = main_storage.getIpcFd() orelse continue;
520 var bytes_read: usize = 0;
521 while (true) {
522 bytes_read += posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) {
523 error.WouldBlock => break,
524 else => |e| {
525 std.log.warn("failed to read child progress data: {s}", .{@errorName(e)});
526 main_storage.completed_count = 0;
527 main_storage.estimated_total_count = 0;
528 continue;
529 },
530 };
531 }
532 // Ignore all but the last message on the pipe.
533 var input: []align(2) u8 = pipe_buf[0..bytes_read];
534 if (input.len == 0) {
535 main_storage.completed_count = 0;
536 main_storage.estimated_total_count = 0;
537 continue;
538 }
539
540 const storage, const parents = while (true) {
541 if (input.len < 4) {
542 std.log.warn("short read: {d} out of 4 header bytes", .{input.len});
543 main_storage.completed_count = 0;
544 main_storage.estimated_total_count = 0;
545 continue;
546 }
547 const subtree_len = std.mem.readInt(u32, input[0..4], .little);
548 const expected_bytes = 4 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
549 if (input.len < expected_bytes) {
550 std.log.warn("short read: {d} out of {d} ({d} nodes)", .{ input.len, expected_bytes, subtree_len });
551 main_storage.completed_count = 0;
552 main_storage.estimated_total_count = 0;
553 continue;
554 }
555 if (input.len > expected_bytes) {
556 input = @alignCast(input[expected_bytes..]);
557 continue;
558 }
559 const storage_bytes = input[4..][0 .. subtree_len * @sizeOf(Node.Storage)];
560 const parents_bytes = input[4 + storage_bytes.len ..][0 .. subtree_len * @sizeOf(Node.Parent)];
561 break .{
562 std.mem.bytesAsSlice(Node.Storage, storage_bytes),
563 std.mem.bytesAsSlice(Node.Parent, parents_bytes),
564 };
565 };
566
567 // Mount the root here.
568 main_storage.* = storage[0];
569
570 // Copy the rest of the tree to the end.
571 @memcpy(serialized_node_storage_buffer[serialized_len..][0 .. storage.len - 1], storage[1..]);
572
573 // Patch up parent pointers taking into account how the subtree is mounted.
574 serialized_node_parents_buffer[serialized_len] = .none;
575
576 for (serialized_node_parents_buffer[serialized_len..][0 .. parents.len - 1], parents[1..]) |*dest, p| {
577 dest.* = switch (p) {
578 // Fix bad data so the rest of the code does not see `unused`.
579 .none, .unused => .none,
580 // Root node is being mounted here.
581 @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),
582 // Other nodes mounted at the end.
583 _ => |off| @enumFromInt(serialized_len + @intFromEnum(off) - 1),
584 };
585 }
586
587 serialized_len += storage.len - 1;
588 }
589
493 return .{590 return .{
494 .parents = serialized_node_parents,591 .parents = serialized_node_parents_buffer[0..serialized_len],
495 .storage = serialized_node_storage,592 .storage = serialized_node_storage_buffer[0..serialized_len],
496 };593 };
497}594}
498595
499fn computeRedraw() []u8 {596fn computeRedraw() []u8 {
500 const serialized = serialize();597 const serialized = serialize();
501598
599 // Now we can analyze our copy of the graph without atomics, reconstructing
600 // children lists which do not exist in the canonical data. These are
601 // needed for tree traversal below.
602
502 var children_buffer: [default_node_storage_buffer_len]Children = undefined;603 var children_buffer: [default_node_storage_buffer_len]Children = undefined;
503 const children = children_buffer[0..serialized.parents.len];604 const children = children_buffer[0..serialized.parents.len];
504605
...@@ -624,7 +725,8 @@ fn write(buf: []const u8) void {...@@ -624,7 +725,8 @@ fn write(buf: []const u8) void {
624725
625fn writeIpc(fd: posix.fd_t, serialized: Serialized) void {726fn writeIpc(fd: posix.fd_t, serialized: Serialized) void {
626 assert(serialized.parents.len == serialized.storage.len);727 assert(serialized.parents.len == serialized.storage.len);
627 const header = std.mem.asBytes(&serialized.parents.len);728 const serialized_len: u32 = @intCast(serialized.parents.len);
729 const header = std.mem.asBytes(&serialized_len);
628 const storage = std.mem.sliceAsBytes(serialized.storage);730 const storage = std.mem.sliceAsBytes(serialized.storage);
629 const parents = std.mem.sliceAsBytes(serialized.parents);731 const parents = std.mem.sliceAsBytes(serialized.parents);
630732
...@@ -637,10 +739,17 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) void {...@@ -637,10 +739,17 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) void {
637 // TODO: if big endian, byteswap739 // TODO: if big endian, byteswap
638 // this is needed because the parent or child process might be running in qemu740 // this is needed because the parent or child process might be running in qemu
639741
640 const file: std.fs.File = .{ .handle = fd };742 // If this write would block we do not want to keep trying, but we need to
641 file.writevAll(&vecs) catch |err| {743 // know if a partial message was written.
642 std.log.warn("failed to send progress to parent process: {s}", .{@errorName(err)});744 if (posix.writev(fd, &vecs)) |written| {
643 };745 const total = header.len + storage.len + parents.len;
746 if (written < total) {
747 std.log.warn("short write: {d} out of {d}", .{ written, total });
748 }
749 } else |err| switch (err) {
750 error.WouldBlock => {},
751 else => |e| std.log.warn("failed to send progress to parent process: {s}", .{@errorName(e)}),
752 }
644}753}
645754
646fn maybeUpdateSize(resize_flag: bool) void {755fn maybeUpdateSize(resize_flag: bool) void {
lib/std/process/Child.zig+17-12
...@@ -98,7 +98,10 @@ resource_usage_statistics: ResourceUsageStatistics = .{},...@@ -98,7 +98,10 @@ resource_usage_statistics: ResourceUsageStatistics = .{},
98/// write end of the pipe will be specified in the `ZIG_PROGRESS`98/// write end of the pipe will be specified in the `ZIG_PROGRESS`
99/// environment variable inside the child process. The progress reported by99/// environment variable inside the child process. The progress reported by
100/// the child will be attached to this progress node in the parent process.100/// the child will be attached to this progress node in the parent process.
101parent_progress_node: std.Progress.Node = .{ .index = .none },101///
102/// The child's progress tree will be grafted into the parent's progress tree,
103/// by substituting this node with the child's root node.
104progress_node: std.Progress.Node = .{ .index = .none },
102105
103pub const ResourceUsageStatistics = struct {106pub const ResourceUsageStatistics = struct {
104 rusage: @TypeOf(rusage_init) = rusage_init,107 rusage: @TypeOf(rusage_init) = rusage_init,
...@@ -581,11 +584,11 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -581,11 +584,11 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
581 }584 }
582585
583 const prog_pipe: [2]posix.fd_t = p: {586 const prog_pipe: [2]posix.fd_t = p: {
584 if (self.parent_progress_node.index == .none) {587 if (self.progress_node.index == .none) {
585 break :p .{ -1, -1 };588 break :p .{ -1, -1 };
586 } else {589 } else {
587 // No CLOEXEC because the child needs access to this file descriptor.590 // No CLOEXEC because the child needs access to this file descriptor.
588 break :p try posix.pipe2(.{});591 break :p try posix.pipe2(.{ .NONBLOCK = true });
589 }592 }
590 };593 };
591 errdefer destroyPipe(prog_pipe);594 errdefer destroyPipe(prog_pipe);
...@@ -685,18 +688,18 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -685,18 +688,18 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
685688
686 // we are the parent689 // we are the parent
687 const pid: i32 = @intCast(pid_result);690 const pid: i32 = @intCast(pid_result);
688 if (self.stdin_behavior == StdIo.Pipe) {691 if (self.stdin_behavior == .Pipe) {
689 self.stdin = File{ .handle = stdin_pipe[1] };692 self.stdin = .{ .handle = stdin_pipe[1] };
690 } else {693 } else {
691 self.stdin = null;694 self.stdin = null;
692 }695 }
693 if (self.stdout_behavior == StdIo.Pipe) {696 if (self.stdout_behavior == .Pipe) {
694 self.stdout = File{ .handle = stdout_pipe[0] };697 self.stdout = .{ .handle = stdout_pipe[0] };
695 } else {698 } else {
696 self.stdout = null;699 self.stdout = null;
697 }700 }
698 if (self.stderr_behavior == StdIo.Pipe) {701 if (self.stderr_behavior == .Pipe) {
699 self.stderr = File{ .handle = stderr_pipe[0] };702 self.stderr = .{ .handle = stderr_pipe[0] };
700 } else {703 } else {
701 self.stderr = null;704 self.stderr = null;
702 }705 }
...@@ -705,15 +708,17 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {...@@ -705,15 +708,17 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
705 self.err_pipe = err_pipe;708 self.err_pipe = err_pipe;
706 self.term = null;709 self.term = null;
707710
708 if (self.stdin_behavior == StdIo.Pipe) {711 if (self.stdin_behavior == .Pipe) {
709 posix.close(stdin_pipe[0]);712 posix.close(stdin_pipe[0]);
710 }713 }
711 if (self.stdout_behavior == StdIo.Pipe) {714 if (self.stdout_behavior == .Pipe) {
712 posix.close(stdout_pipe[1]);715 posix.close(stdout_pipe[1]);
713 }716 }
714 if (self.stderr_behavior == StdIo.Pipe) {717 if (self.stderr_behavior == .Pipe) {
715 posix.close(stderr_pipe[1]);718 posix.close(stderr_pipe[1]);
716 }719 }
720
721 self.progress_node.setIpcFd(prog_pipe[0]);
717}722}
718723
719fn spawnWindows(self: *ChildProcess) SpawnError!void {724fn spawnWindows(self: *ChildProcess) SpawnError!void {