| ... | ... | @@ -83,6 +83,22 @@ pub const Node = struct { |
| 83 | 83 | /// Little endian. |
| 84 | 84 | estimated_total_count: u32, |
| 85 | 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 | }; |
| 87 | 103 | |
| 88 | 104 | const Parent = enum(u16) { |
| ... | ... | @@ -201,6 +217,13 @@ pub const Node = struct { |
| 201 | 217 | } |
| 202 | 218 | } |
| 203 | 219 | |
| 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 | 227 | fn storageByIndex(index: Node.Index) *Node.Storage { |
| 205 | 228 | return &global_progress.node_storage[@intFromEnum(index)]; |
| 206 | 229 | } |
| ... | ... | @@ -475,14 +498,8 @@ fn serialize() Serialized { |
| 475 | 498 | } |
| 476 | 499 | } |
| 477 | 500 | |
| 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 | 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 | 503 | parent.* = switch (parent.*) { |
| 487 | 504 | .unused => unreachable, |
| 488 | 505 | .none => .none, |
| ... | ... | @@ -490,15 +507,99 @@ fn serialize() Serialized { |
| 490 | 507 | }; |
| 491 | 508 | } |
| 492 | 509 | |
| 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 | 590 | return .{ |
| 494 | | .parents = serialized_node_parents, |
| 495 | | .storage = serialized_node_storage, |
| 591 | .parents = serialized_node_parents_buffer[0..serialized_len], |
| 592 | .storage = serialized_node_storage_buffer[0..serialized_len], |
| 496 | 593 | }; |
| 497 | 594 | } |
| 498 | 595 | |
| 499 | 596 | fn computeRedraw() []u8 { |
| 500 | 597 | const serialized = serialize(); |
| 501 | 598 | |
| 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 | 603 | var children_buffer: [default_node_storage_buffer_len]Children = undefined; |
| 503 | 604 | const children = children_buffer[0..serialized.parents.len]; |
| 504 | 605 | |
| ... | ... | @@ -624,7 +725,8 @@ fn write(buf: []const u8) void { |
| 624 | 725 | |
| 625 | 726 | fn writeIpc(fd: posix.fd_t, serialized: Serialized) void { |
| 626 | 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 | 730 | const storage = std.mem.sliceAsBytes(serialized.storage); |
| 629 | 731 | const parents = std.mem.sliceAsBytes(serialized.parents); |
| 630 | 732 | |
| ... | ... | @@ -637,10 +739,17 @@ fn writeIpc(fd: posix.fd_t, serialized: Serialized) void { |
| 637 | 739 | // TODO: if big endian, byteswap |
| 638 | 740 | // this is needed because the parent or child process might be running in qemu |
| 639 | 741 | |
| 640 | | const file: std.fs.File = .{ .handle = fd }; |
| 641 | | file.writevAll(&vecs) catch |err| { |
| 642 | | std.log.warn("failed to send progress to parent process: {s}", .{@errorName(err)}); |
| 643 | | }; |
| 742 | // If this write would block we do not want to keep trying, but we need to |
| 743 | // know if a partial message was written. |
| 744 | if (posix.writev(fd, &vecs)) |written| { |
| 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 | } |
| 645 | 754 | |
| 646 | 755 | fn maybeUpdateSize(resize_flag: bool) void { |