| ... | @@ -11,7 +11,7 @@ const windows = std.os.windows; | ... | @@ -11,7 +11,7 @@ const windows = std.os.windows; |
| 11 | const testing = std.testing; | 11 | const testing = std.testing; |
| 12 | const assert = std.debug.assert; | 12 | const assert = std.debug.assert; |
| 13 | const posix = std.posix; | 13 | const posix = std.posix; |
| 14 | const Writer = std.Io.Writer; | 14 | const Writer = Io.Writer; |
| 15 | | 15 | |
| 16 | /// Currently this API only supports this value being set to stderr, which | 16 | /// Currently this API only supports this value being set to stderr, which |
| 17 | /// happens automatically inside `start`. | 17 | /// happens automatically inside `start`. |
| ... | @@ -21,13 +21,10 @@ io: Io, | ... | @@ -21,13 +21,10 @@ io: Io, |
| 21 | | 21 | |
| 22 | terminal_mode: TerminalMode, | 22 | terminal_mode: TerminalMode, |
| 23 | | 23 | |
| 24 | update_worker: ?Io.Future(void), | 24 | update_worker: ?Io.Future(WorkerError!void), |
| 25 | | 25 | |
| 26 | /// Atomically set by SIGWINCH as well as the root done() function. | 26 | /// Atomically set by SIGWINCH as well as the root done() function. |
| 27 | redraw_event: Io.Event, | 27 | redraw_event: Io.Event, |
| 28 | /// Indicates a request to shut down and reset global state. | | |
| 29 | /// Accessed atomically. | | |
| 30 | done: bool, | | |
| 31 | need_clear: bool, | 28 | need_clear: bool, |
| 32 | status: Status, | 29 | status: Status, |
| 33 | | 30 | |
| ... | @@ -43,15 +40,19 @@ draw_buffer: []u8, | ... | @@ -43,15 +40,19 @@ draw_buffer: []u8, |
| 43 | /// This is in a separate array from `node_storage` but with the same length so | 40 | /// This is in a separate array from `node_storage` but with the same length so |
| 44 | /// that it can be iterated over efficiently without trashing too much of the | 41 | /// that it can be iterated over efficiently without trashing too much of the |
| 45 | /// CPU cache. | 42 | /// CPU cache. |
| 46 | node_parents: []Node.Parent, | 43 | node_parents: [node_storage_buffer_len]Node.Parent, |
| 47 | node_storage: []Node.Storage, | 44 | node_storage: [node_storage_buffer_len]Node.Storage, |
| 48 | node_freelist_next: []Node.OptionalIndex, | 45 | node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex, |
| 49 | node_freelist: Freelist, | 46 | node_freelist: Freelist, |
| 50 | /// This is the number of elements in node arrays which have been used so far. Nodes before this | 47 | /// This is the number of elements in node arrays which have been used so far. Nodes before this |
| 51 | /// index are either active, or on the freelist. The remaining nodes are implicitly free. This | 48 | /// index are either active, or on the freelist. The remaining nodes are implicitly free. This |
| 52 | /// value may at times temporarily exceed the node count. | 49 | /// value may at times temporarily exceed the node count. |
| 53 | node_end_index: u32, | 50 | node_end_index: u32, |
| 54 | | 51 | |
| | 52 | ipc_next: Ipc.SlotAtomic, |
| | 53 | ipc: [ipc_storage_buffer_len]Ipc, |
| | 54 | ipc_files: [ipc_storage_buffer_len]Io.File, |
| | 55 | |
| 55 | start_failure: StartFailure, | 56 | start_failure: StartFailure, |
| 56 | | 57 | |
| 57 | pub const Status = enum { | 58 | pub const Status = enum { |
| ... | @@ -77,6 +78,80 @@ const Freelist = packed struct(u32) { | ... | @@ -77,6 +78,80 @@ const Freelist = packed struct(u32) { |
| 77 | generation: u24, | 78 | generation: u24, |
| 78 | }; | 79 | }; |
| 79 | | 80 | |
| | 81 | pub const Ipc = packed struct(u32) { |
| | 82 | /// mutex protecting `file` use, only locked by `serializeIpc` |
| | 83 | locked: bool, |
| | 84 | /// when unlocked: whether `file` is defined |
| | 85 | /// when locked: whether `file` does not need to be closed |
| | 86 | valid: bool, |
| | 87 | unused: @Int(.unsigned, 32 - 2 - @bitSizeOf(Generation)) = 0, |
| | 88 | generation: Generation, |
| | 89 | |
| | 90 | pub const Slot = std.math.IntFittingRange(0, ipc_storage_buffer_len - 1); |
| | 91 | pub const Generation = @Int(.unsigned, 32 - @bitSizeOf(Slot)); |
| | 92 | |
| | 93 | const SlotAtomic = @Int(.unsigned, std.math.ceilPowerOfTwoAssert(usize, @min(@bitSizeOf(Slot), 8))); |
| | 94 | |
| | 95 | pub const Index = packed struct(u32) { |
| | 96 | slot: Slot, |
| | 97 | generation: Generation, |
| | 98 | }; |
| | 99 | |
| | 100 | const Data = struct { |
| | 101 | state: State, |
| | 102 | bytes_read: u16, |
| | 103 | main_index: u8, |
| | 104 | start_index: u8, |
| | 105 | nodes_len: u8, |
| | 106 | |
| | 107 | const State = enum { unused, pending, ready }; |
| | 108 | |
| | 109 | /// No operations have been started on this file. |
| | 110 | const unused: Data = .{ |
| | 111 | .state = .unused, |
| | 112 | .bytes_read = 0, |
| | 113 | .main_index = 0, |
| | 114 | .start_index = 0, |
| | 115 | .nodes_len = 0, |
| | 116 | }; |
| | 117 | |
| | 118 | fn findLastPacket(data: *const Data, buffer: *const [max_packet_len]u8) struct { u16, u16 } { |
| | 119 | assert(data.state == .ready); |
| | 120 | var packet_start: u16 = 0; |
| | 121 | var packet_end: u16 = 0; |
| | 122 | const bytes_read = data.bytes_read; |
| | 123 | while (bytes_read - packet_end >= 1) { |
| | 124 | const nodes_len: u16 = buffer[packet_end]; |
| | 125 | const packet_len = 1 + nodes_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent)); |
| | 126 | if (packet_end + packet_len > bytes_read) break; |
| | 127 | packet_start = packet_end; |
| | 128 | packet_end += packet_len; |
| | 129 | } |
| | 130 | return .{ packet_start, packet_end }; |
| | 131 | } |
| | 132 | |
| | 133 | fn rebase( |
| | 134 | data: *Data, |
| | 135 | buffer: *[max_packet_len]u8, |
| | 136 | vec: *[1][]u8, |
| | 137 | batch: *std.Io.Batch, |
| | 138 | slot: Slot, |
| | 139 | packet_end: u16, |
| | 140 | ) void { |
| | 141 | assert(data.state == .ready); |
| | 142 | const remaining = buffer[packet_end..data.bytes_read]; |
| | 143 | @memmove(buffer[0..remaining.len], remaining); |
| | 144 | vec.* = .{buffer[remaining.len..]}; |
| | 145 | batch.addAt(slot, .{ .file_read_streaming = .{ |
| | 146 | .file = global_progress.ipc_files[slot], |
| | 147 | .data = vec, |
| | 148 | } }); |
| | 149 | data.state = .pending; |
| | 150 | data.bytes_read = @intCast(remaining.len); |
| | 151 | } |
| | 152 | }; |
| | 153 | }; |
| | 154 | |
| 80 | pub const TerminalMode = union(enum) { | 155 | pub const TerminalMode = union(enum) { |
| 81 | off, | 156 | off, |
| 82 | ansi_escape_codes, | 157 | ansi_escape_codes, |
| ... | @@ -116,7 +191,7 @@ pub const Node = struct { | ... | @@ -116,7 +191,7 @@ pub const Node = struct { |
| 116 | | 191 | |
| 117 | pub const none: Node = .{ .index = .none }; | 192 | pub const none: Node = .{ .index = .none }; |
| 118 | | 193 | |
| 119 | pub const max_name_len = 40; | 194 | pub const max_name_len = 120; |
| 120 | | 195 | |
| 121 | const Storage = extern struct { | 196 | const Storage = extern struct { |
| 122 | /// Little endian. | 197 | /// Little endian. |
| ... | @@ -127,25 +202,16 @@ pub const Node = struct { | ... | @@ -127,25 +202,16 @@ pub const Node = struct { |
| 127 | name: [max_name_len]u8 align(@alignOf(usize)), | 202 | name: [max_name_len]u8 align(@alignOf(usize)), |
| 128 | | 203 | |
| 129 | /// Not thread-safe. | 204 | /// Not thread-safe. |
| 130 | fn getIpcFd(s: Storage) ?Io.File.Handle { | 205 | fn getIpcIndex(s: Storage) ?Ipc.Index { |
| 131 | return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) { | 206 | return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null; |
| 132 | .int => @bitCast(s.completed_count), | | |
| 133 | .pointer => @ptrFromInt(s.completed_count), | | |
| 134 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | | |
| 135 | } else null; | | |
| 136 | } | 207 | } |
| 137 | | 208 | |
| 138 | /// Thread-safe. | 209 | /// Thread-safe. |
| 139 | fn setIpcFd(s: *Storage, fd: Io.File.Handle) void { | 210 | fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) void { |
| 140 | const integer: u32 = switch (@typeInfo(Io.File.Handle)) { | | |
| 141 | .int => @bitCast(fd), | | |
| 142 | .pointer => @intCast(@intFromPtr(fd)), | | |
| 143 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | | |
| 144 | }; | | |
| 145 | // `estimated_total_count` max int indicates the special state that | 211 | // `estimated_total_count` max int indicates the special state that |
| 146 | // causes `completed_count` to be treated as a file descriptor, so | 212 | // causes `completed_count` to be treated as a file descriptor, so |
| 147 | // the order here matters. | 213 | // the order here matters. |
| 148 | @atomicStore(u32, &s.completed_count, integer, .monotonic); | 214 | @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic); |
| 149 | @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize` | 215 | @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize` |
| 150 | } | 216 | } |
| 151 | | 217 | |
| ... | @@ -155,6 +221,14 @@ pub const Node = struct { | ... | @@ -155,6 +221,14 @@ pub const Node = struct { |
| 155 | s.estimated_total_count = @byteSwap(s.estimated_total_count); | 221 | s.estimated_total_count = @byteSwap(s.estimated_total_count); |
| 156 | } | 222 | } |
| 157 | | 223 | |
| | 224 | fn copyRoot(dest: *Node.Storage, src: *align(1) const Node.Storage) void { |
| | 225 | dest.* = .{ |
| | 226 | .completed_count = src.completed_count, |
| | 227 | .estimated_total_count = src.estimated_total_count, |
| | 228 | .name = if (src.name[0] == 0) dest.name else src.name, |
| | 229 | }; |
| | 230 | } |
| | 231 | |
| 158 | comptime { | 232 | comptime { |
| 159 | assert((@sizeOf(Storage) % 4) == 0); | 233 | assert((@sizeOf(Storage) % 4) == 0); |
| 160 | } | 234 | } |
| ... | @@ -242,7 +316,7 @@ pub const Node = struct { | ... | @@ -242,7 +316,7 @@ pub const Node = struct { |
| 242 | } | 316 | } |
| 243 | | 317 | |
| 244 | const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic); | 318 | const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic); |
| 245 | if (free_index >= global_progress.node_storage.len) { | 319 | if (free_index >= node_storage_buffer_len) { |
| 246 | // Ran out of node storage memory. Progress for this node will not be tracked. | 320 | // Ran out of node storage memory. Progress for this node will not be tracked. |
| 247 | _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic); | 321 | _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic); |
| 248 | return Node.none; | 322 | return Node.none; |
| ... | @@ -292,15 +366,17 @@ pub const Node = struct { | ... | @@ -292,15 +366,17 @@ pub const Node = struct { |
| 292 | const index = n.index.unwrap() orelse return; | 366 | const index = n.index.unwrap() orelse return; |
| 293 | const storage = storageByIndex(index); | 367 | const storage = storageByIndex(index); |
| 294 | // Avoid u32 max int which is used to indicate a special state. | 368 | // Avoid u32 max int which is used to indicate a special state. |
| 295 | const saturated = @min(std.math.maxInt(u32) - 1, count); | 369 | const saturated_total_count = @min(std.math.maxInt(u32) - 1, count); |
| 296 | @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic); | 370 | @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic); |
| 297 | } | 371 | } |
| 298 | | 372 | |
| 299 | /// Thread-safe. | 373 | /// Thread-safe. |
| 300 | pub fn increaseEstimatedTotalItems(n: Node, count: usize) void { | 374 | pub fn increaseEstimatedTotalItems(n: Node, count: usize) void { |
| 301 | const index = n.index.unwrap() orelse return; | 375 | const index = n.index.unwrap() orelse return; |
| 302 | const storage = storageByIndex(index); | 376 | const storage = storageByIndex(index); |
| 303 | _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, std.math.lossyCast(u32, count), .monotonic); | 377 | // Avoid u32 max int which is used to indicate a special state. |
| | 378 | const saturated_total_count = @min(std.math.maxInt(u32) - 1, count); |
| | 379 | _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, saturated_total_count, .monotonic); |
| 304 | } | 380 | } |
| 305 | | 381 | |
| 306 | /// Finish a started `Node`. Thread-safe. | 382 | /// Finish a started `Node`. Thread-safe. |
| ... | @@ -310,11 +386,25 @@ pub const Node = struct { | ... | @@ -310,11 +386,25 @@ pub const Node = struct { |
| 310 | return; | 386 | return; |
| 311 | } | 387 | } |
| 312 | const index = n.index.unwrap() orelse return; | 388 | const index = n.index.unwrap() orelse return; |
| | 389 | const io = global_progress.io; |
| 313 | const parent_ptr = parentByIndex(index); | 390 | const parent_ptr = parentByIndex(index); |
| 314 | if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| { | 391 | if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| { |
| 315 | _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic); | 392 | _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic); |
| 316 | @atomicStore(Node.Parent, parent_ptr, .unused, .monotonic); | 393 | @atomicStore(Node.Parent, parent_ptr, .unused, .monotonic); |
| 317 | | 394 | |
| | 395 | if (storageByIndex(index).getIpcIndex()) |ipc_index| { |
| | 396 | const file = global_progress.ipc_files[ipc_index.slot]; |
| | 397 | const ipc = @atomicRmw( |
| | 398 | Ipc, |
| | 399 | &global_progress.ipc[ipc_index.slot], |
| | 400 | .And, |
| | 401 | .{ .locked = true, .valid = false, .generation = std.math.maxInt(Ipc.Generation) }, |
| | 402 | .release, |
| | 403 | ); |
| | 404 | assert(ipc.valid and ipc.generation == ipc_index.generation); |
| | 405 | if (!ipc.locked) file.close(io); |
| | 406 | } |
| | 407 | |
| 318 | const freelist = &global_progress.node_freelist; | 408 | const freelist = &global_progress.node_freelist; |
| 319 | var old_freelist = @atomicLoad(Freelist, freelist, .monotonic); | 409 | var old_freelist = @atomicLoad(Freelist, freelist, .monotonic); |
| 320 | while (true) { | 410 | while (true) { |
| ... | @@ -332,42 +422,52 @@ pub const Node = struct { | ... | @@ -332,42 +422,52 @@ pub const Node = struct { |
| 332 | }; | 422 | }; |
| 333 | } | 423 | } |
| 334 | } else { | 424 | } else { |
| 335 | @atomicStore(bool, &global_progress.done, true, .monotonic); | 425 | if (global_progress.update_worker) |*worker| worker.cancel(io) catch {}; |
| 336 | const io = global_progress.io; | 426 | for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| { |
| 337 | global_progress.redraw_event.set(io); | 427 | assert(!ipc.locked or !ipc.valid); // missing call to end() |
| 338 | if (global_progress.update_worker) |*worker| worker.await(io); | 428 | if (ipc.locked or ipc.valid) ipc_file.close(io); |
| | 429 | } |
| 339 | } | 430 | } |
| 340 | } | 431 | } |
| 341 | | 432 | |
| 342 | /// Posix-only. Used by `std.process.Child`. Thread-safe. | 433 | /// Used by `std.process.Child`. Thread-safe. |
| 343 | pub fn setIpcFd(node: Node, fd: Io.File.Handle) void { | 434 | pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void { |
| 344 | const index = node.index.unwrap() orelse return; | 435 | const index = node.index.unwrap() orelse return; |
| 345 | switch (@typeInfo(Io.File.Handle)) { | 436 | const io = global_progress.io; |
| 346 | .int => { | 437 | assert(io.userdata == expected_io_userdata); |
| 347 | assert(fd >= 0); | 438 | for (0..ipc_storage_buffer_len) |_| { |
| 348 | assert(fd != posix.STDOUT_FILENO); | 439 | const slot: Ipc.Slot = @truncate( |
| 349 | assert(fd != posix.STDIN_FILENO); | 440 | @atomicRmw(Ipc.SlotAtomic, &global_progress.ipc_next, .Add, 1, .monotonic), |
| 350 | assert(fd != posix.STDERR_FILENO); | 441 | ); |
| 351 | }, | 442 | if (slot >= ipc_storage_buffer_len) continue; |
| 352 | .pointer => { | 443 | const ipc_ptr = &global_progress.ipc[slot]; |
| 353 | assert(fd != windows.INVALID_HANDLE_VALUE); | 444 | const ipc = @atomicLoad(Ipc, ipc_ptr, .monotonic); |
| 354 | }, | 445 | if (ipc.locked or ipc.valid) continue; |
| 355 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | 446 | const generation = ipc.generation +% 1; |
| 356 | } | 447 | if (@cmpxchgWeak( |
| 357 | storageByIndex(index).setIpcFd(fd); | 448 | Ipc, |
| | 449 | ipc_ptr, |
| | 450 | ipc, |
| | 451 | .{ .locked = false, .valid = true, .generation = generation }, |
| | 452 | .acquire, |
| | 453 | .monotonic, |
| | 454 | )) |_| continue; |
| | 455 | global_progress.ipc_files[slot] = file; |
| | 456 | storageByIndex(index).setIpcIndex(.{ .slot = slot, .generation = generation }); |
| | 457 | break; |
| | 458 | } else file.close(io); |
| 358 | } | 459 | } |
| 359 | | 460 | |
| 360 | /// Posix-only. Thread-safe. Assumes the node is storing an IPC file | 461 | pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void { |
| 361 | /// descriptor. | 462 | storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index); |
| 362 | pub fn getIpcFd(node: Node) ?Io.File.Handle { | 463 | } |
| 363 | const index = node.index.unwrap() orelse return null; | 464 | |
| 364 | const storage = storageByIndex(index); | 465 | /// Not thread-safe. |
| 365 | const int = @atomicLoad(u32, &storage.completed_count, .monotonic); | 466 | pub fn takeIpcIndex(node: Node) ?Ipc.Index { |
| 366 | return switch (@typeInfo(Io.File.Handle)) { | 467 | const storage = storageByIndex(node.index.unwrap() orelse return null); |
| 367 | .int => @bitCast(int), | 468 | assert(storage.estimated_total_count == std.math.maxInt(u32)); |
| 368 | .pointer => @ptrFromInt(int), | 469 | @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic); |
| 369 | else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), | 470 | return @bitCast(storage.completed_count); |
| 370 | }; | | |
| 371 | } | 471 | } |
| 372 | | 472 | |
| 373 | fn storageByIndex(index: Node.Index) *Node.Storage { | 473 | fn storageByIndex(index: Node.Index) *Node.Storage { |
| ... | @@ -387,7 +487,9 @@ pub const Node = struct { | ... | @@ -387,7 +487,9 @@ pub const Node = struct { |
| 387 | | 487 | |
| 388 | const storage = storageByIndex(free_index); | 488 | const storage = storageByIndex(free_index); |
| 389 | @atomicStore(u32, &storage.completed_count, 0, .monotonic); | 489 | @atomicStore(u32, &storage.completed_count, 0, .monotonic); |
| 390 | @atomicStore(u32, &storage.estimated_total_count, std.math.lossyCast(u32, estimated_total_items), .monotonic); | 490 | // Avoid u32 max int which is used to indicate a special state. |
| | 491 | const saturated_total_count = @min(std.math.maxInt(u32) - 1, estimated_total_items); |
| | 492 | @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic); |
| 391 | const name_len = @min(max_name_len, name.len); | 493 | const name_len = @min(max_name_len, name.len); |
| 392 | copyAtomicStore(storage.name[0..name_len], name[0..name_len]); | 494 | copyAtomicStore(storage.name[0..name_len], name[0..name_len]); |
| 393 | if (name_len < storage.name.len) | 495 | if (name_len < storage.name.len) |
| ... | @@ -414,16 +516,20 @@ var global_progress: Progress = .{ | ... | @@ -414,16 +516,20 @@ var global_progress: Progress = .{ |
| 414 | .rows = 0, | 516 | .rows = 0, |
| 415 | .cols = 0, | 517 | .cols = 0, |
| 416 | .draw_buffer = undefined, | 518 | .draw_buffer = undefined, |
| 417 | .done = false, | | |
| 418 | .need_clear = false, | 519 | .need_clear = false, |
| 419 | .status = .working, | 520 | .status = .working, |
| 420 | .start_failure = .unstarted, | | |
| 421 | | 521 | |
| 422 | .node_parents = &node_parents_buffer, | 522 | .node_parents = undefined, |
| 423 | .node_storage = &node_storage_buffer, | 523 | .node_storage = undefined, |
| 424 | .node_freelist_next = &node_freelist_next_buffer, | 524 | .node_freelist_next = undefined, |
| 425 | .node_freelist = .{ .head = .none, .generation = 0 }, | 525 | .node_freelist = .{ .head = .none, .generation = 0 }, |
| 426 | .node_end_index = 0, | 526 | .node_end_index = 0, |
| | 527 | |
| | 528 | .ipc_next = 0, |
| | 529 | .ipc = undefined, |
| | 530 | .ipc_files = undefined, |
| | 531 | |
| | 532 | .start_failure = .unstarted, |
| 427 | }; | 533 | }; |
| 428 | | 534 | |
| 429 | pub const StartFailure = union(enum) { | 535 | pub const StartFailure = union(enum) { |
| ... | @@ -433,17 +539,23 @@ pub const StartFailure = union(enum) { | ... | @@ -433,17 +539,23 @@ pub const StartFailure = union(enum) { |
| 433 | parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat }, | 539 | parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat }, |
| 434 | }; | 540 | }; |
| 435 | | 541 | |
| 436 | const node_storage_buffer_len = 83; | 542 | /// One less than a power of two ensures `max_packet_len` is already a power of two. |
| 437 | var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined; | 543 | const node_storage_buffer_len = ipc_storage_buffer_len - 1; |
| 438 | var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined; | 544 | |
| 439 | var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined; | 545 | /// Power of two to avoid wasted `ipc_next` increments. |
| | 546 | const ipc_storage_buffer_len = 128; |
| | 547 | |
| | 548 | pub const max_packet_len = std.math.ceilPowerOfTwoAssert( |
| | 549 | usize, |
| | 550 | 1 + node_storage_buffer_len * (@sizeOf(Node.Storage) + @sizeOf(Node.OptionalIndex)), |
| | 551 | ); |
| 440 | | 552 | |
| 441 | var default_draw_buffer: [4096]u8 = undefined; | 553 | var default_draw_buffer: [4096]u8 = undefined; |
| 442 | | 554 | |
| 443 | var debug_start_trace = std.debug.Trace.init; | 555 | var debug_start_trace = std.debug.Trace.init; |
| 444 | | 556 | |
| 445 | pub const have_ipc = switch (builtin.os.tag) { | 557 | pub const have_ipc = switch (builtin.os.tag) { |
| 446 | .wasi, .freestanding, .windows => false, | 558 | .wasi, .freestanding => false, |
| 447 | else => true, | 559 | else => true, |
| 448 | }; | 560 | }; |
| 449 | | 561 | |
| ... | @@ -475,9 +587,9 @@ pub fn start(io: Io, options: Options) Node { | ... | @@ -475,9 +587,9 @@ pub fn start(io: Io, options: Options) Node { |
| 475 | } | 587 | } |
| 476 | debug_start_trace.add("first initialized here"); | 588 | debug_start_trace.add("first initialized here"); |
| 477 | | 589 | |
| 478 | @memset(global_progress.node_parents, .unused); | 590 | @memset(&global_progress.node_parents, .unused); |
| | 591 | @memset(&global_progress.ipc, .{ .locked = false, .valid = false, .generation = 0 }); |
| 479 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); | 592 | const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); |
| 480 | global_progress.done = false; | | |
| 481 | global_progress.node_end_index = 1; | 593 | global_progress.node_end_index = 1; |
| 482 | | 594 | |
| 483 | assert(options.draw_buffer.len >= 200); | 595 | assert(options.draw_buffer.len >= 200); |
| ... | @@ -551,58 +663,55 @@ pub fn setStatus(new_status: Status) void { | ... | @@ -551,58 +663,55 @@ pub fn setStatus(new_status: Status) void { |
| 551 | } | 663 | } |
| 552 | | 664 | |
| 553 | /// Returns whether a resize is needed to learn the terminal size. | 665 | /// Returns whether a resize is needed to learn the terminal size. |
| 554 | fn wait(io: Io, timeout_ns: u64) bool { | 666 | fn wait(io: Io, timeout_ns: u64) Io.Cancelable!bool { |
| 555 | const timeout: Io.Timeout = .{ .duration = .{ | 667 | const timeout: Io.Timeout = .{ .duration = .{ |
| 556 | .clock = .awake, | 668 | .clock = .awake, |
| 557 | .raw = .fromNanoseconds(timeout_ns), | 669 | .raw = .fromNanoseconds(timeout_ns), |
| 558 | } }; | 670 | } }; |
| 559 | const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) { | 671 | const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) { |
| 560 | error.Timeout, error.Canceled => false, | 672 | error.Timeout => false, |
| | 673 | error.Canceled => |e| return e, |
| 561 | }; | 674 | }; |
| 562 | global_progress.redraw_event.reset(); | 675 | global_progress.redraw_event.reset(); |
| 563 | return resize_flag or (global_progress.cols == 0); | 676 | return resize_flag or (global_progress.cols == 0); |
| 564 | } | 677 | } |
| 565 | | 678 | |
| 566 | fn updateTask(io: Io) void { | 679 | const WorkerError = error{WindowTooSmall} || Io.ConcurrentError || Io.Cancelable || |
| | 680 | Io.File.Writer.Error || Io.Operation.FileReadStreaming.Error; |
| | 681 | |
| | 682 | fn updateTask(io: Io) WorkerError!void { |
| 567 | // Store this data in the thread so that it does not need to be part of the | 683 | // Store this data in the thread so that it does not need to be part of the |
| 568 | // linker data of the main executable. | 684 | // linker data of the main executable. |
| 569 | var serialized_buffer: Serialized.Buffer = undefined; | 685 | var serialized_buffer: Serialized.Buffer = undefined; |
| | 686 | serialized_buffer.init(); |
| | 687 | defer serialized_buffer.batch.cancel(io); |
| 570 | | 688 | |
| 571 | // In this function we bypass the wrapper code inside `Io.lockStderr` / | 689 | // In this function we bypass the wrapper code inside `Io.lockStderr` / |
| 572 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. | 690 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. |
| 573 | // We still want to go through the `Io` instance however in case it uses a | 691 | // We still want to go through the `Io` instance however in case it uses a |
| 574 | // task-switching mutex. | 692 | // task-switching mutex. |
| 575 | | 693 | |
| 576 | { | 694 | try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); |
| 577 | const resize_flag = wait(io, global_progress.initial_delay_ns); | 695 | errdefer { |
| 578 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; | 696 | const cancel_protection = io.swapCancelProtection(.blocked); |
| 579 | maybeUpdateSize(io, resize_flag) catch return; | 697 | defer _ = io.swapCancelProtection(cancel_protection); |
| 580 | | 698 | const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { |
| 581 | const buffer, _ = computeRedraw(&serialized_buffer); | 699 | error.Canceled => unreachable, // blocked |
| 582 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | 700 | }; |
| 583 | defer io.unlockStderr(); | 701 | defer io.unlockStderr(); |
| 584 | global_progress.need_clear = true; | 702 | clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; |
| 585 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | | |
| 586 | } | | |
| 587 | } | 703 | } |
| 588 | | | |
| 589 | while (true) { | 704 | while (true) { |
| 590 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | 705 | const buffer, _ = try computeRedraw(io, &serialized_buffer); |
| 591 | | 706 | if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| { |
| 592 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { | | |
| 593 | const stderr = io.vtable.lockStderr(io.userdata, null) catch return; | | |
| 594 | defer io.unlockStderr(); | | |
| 595 | return clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; | | |
| 596 | } | | |
| 597 | | | |
| 598 | maybeUpdateSize(io, resize_flag) catch return; | | |
| 599 | | | |
| 600 | const buffer, _ = computeRedraw(&serialized_buffer); | | |
| 601 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | | |
| 602 | defer io.unlockStderr(); | 707 | defer io.unlockStderr(); |
| 603 | global_progress.need_clear = true; | 708 | global_progress.need_clear = true; |
| 604 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | 709 | locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { |
| | 710 | error.WriteFailed => return locked_stderr.file_writer.err.?, |
| | 711 | }; |
| 605 | } | 712 | } |
| | 713 | |
| | 714 | try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); |
| 606 | } | 715 | } |
| 607 | } | 716 | } |
| 608 | | 717 | |
| ... | @@ -614,79 +723,60 @@ fn windowsApiWriteMarker() void { | ... | @@ -614,79 +723,60 @@ fn windowsApiWriteMarker() void { |
| 614 | _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); | 723 | _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); |
| 615 | } | 724 | } |
| 616 | | 725 | |
| 617 | fn windowsApiUpdateTask(io: Io) void { | 726 | fn windowsApiUpdateTask(io: Io) WorkerError!void { |
| | 727 | // Store this data in the thread so that it does not need to be part of the |
| | 728 | // linker data of the main executable. |
| 618 | var serialized_buffer: Serialized.Buffer = undefined; | 729 | var serialized_buffer: Serialized.Buffer = undefined; |
| | 730 | serialized_buffer.init(); |
| | 731 | defer serialized_buffer.batch.cancel(io); |
| 619 | | 732 | |
| 620 | // In this function we bypass the wrapper code inside `Io.lockStderr` / | 733 | // In this function we bypass the wrapper code inside `Io.lockStderr` / |
| 621 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. | 734 | // `Io.tryLockStderr` in order to avoid clearing the terminal twice. |
| 622 | // We still want to go through the `Io` instance however in case it uses a | 735 | // We still want to go through the `Io` instance however in case it uses a |
| 623 | // task-switching mutex. | 736 | // task-switching mutex. |
| 624 | | 737 | |
| 625 | { | 738 | try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); |
| 626 | const resize_flag = wait(io, global_progress.initial_delay_ns); | 739 | errdefer { |
| 627 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; | 740 | const cancel_protection = io.swapCancelProtection(.blocked); |
| 628 | maybeUpdateSize(io, resize_flag) catch return; | 741 | defer _ = io.swapCancelProtection(cancel_protection); |
| 629 | | 742 | _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { |
| 630 | const buffer, const nl_n = computeRedraw(&serialized_buffer); | 743 | error.Canceled => unreachable, // blocked |
| 631 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | 744 | }; |
| 632 | defer io.unlockStderr(); | 745 | defer io.unlockStderr(); |
| 633 | windowsApiWriteMarker(); | 746 | clearWrittenWindowsApi() catch {}; |
| 634 | global_progress.need_clear = true; | | |
| 635 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | | |
| 636 | windowsApiMoveToMarker(nl_n) catch return; | | |
| 637 | } | | |
| 638 | } | 747 | } |
| 639 | | | |
| 640 | while (true) { | 748 | while (true) { |
| 641 | const resize_flag = wait(io, global_progress.refresh_rate_ns); | 749 | const buffer, const nl_n = try computeRedraw(io, &serialized_buffer); |
| 642 | | | |
| 643 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { | | |
| 644 | _ = io.vtable.lockStderr(io.userdata, null) catch return; | | |
| 645 | defer io.unlockStderr(); | | |
| 646 | return clearWrittenWindowsApi() catch {}; | | |
| 647 | } | | |
| 648 | | | |
| 649 | maybeUpdateSize(io, resize_flag) catch return; | | |
| 650 | | | |
| 651 | const buffer, const nl_n = computeRedraw(&serialized_buffer); | | |
| 652 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { | 750 | if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { |
| 653 | defer io.unlockStderr(); | 751 | defer io.unlockStderr(); |
| 654 | clearWrittenWindowsApi() catch return; | 752 | try clearWrittenWindowsApi(); |
| 655 | windowsApiWriteMarker(); | 753 | windowsApiWriteMarker(); |
| 656 | global_progress.need_clear = true; | 754 | global_progress.need_clear = true; |
| 657 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | 755 | locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { |
| | 756 | error.WriteFailed => return locked_stderr.file_writer.err.?, |
| | 757 | }; |
| 658 | windowsApiMoveToMarker(nl_n) catch return; | 758 | windowsApiMoveToMarker(nl_n) catch return; |
| 659 | } | 759 | } |
| | 760 | |
| | 761 | try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); |
| 660 | } | 762 | } |
| 661 | } | 763 | } |
| 662 | | 764 | |
| 663 | fn ipcThreadRun(io: Io, file: Io.File) void { | 765 | fn ipcThreadRun(io: Io, file: Io.File) WorkerError!void { |
| 664 | // Store this data in the thread so that it does not need to be part of the | 766 | // Store this data in the thread so that it does not need to be part of the |
| 665 | // linker data of the main executable. | 767 | // linker data of the main executable. |
| 666 | var serialized_buffer: Serialized.Buffer = undefined; | 768 | var serialized_buffer: Serialized.Buffer = undefined; |
| | 769 | serialized_buffer.init(); |
| | 770 | defer serialized_buffer.batch.cancel(io); |
| | 771 | var fw = file.writerStreaming(io, &.{}); |
| 667 | | 772 | |
| 668 | { | 773 | _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake); |
| 669 | _ = wait(io, global_progress.initial_delay_ns); | | |
| 670 | | | |
| 671 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) | | |
| 672 | return; | | |
| 673 | | | |
| 674 | const serialized = serialize(&serialized_buffer); | | |
| 675 | writeIpc(io, file, serialized) catch |err| switch (err) { | | |
| 676 | error.BrokenPipe => return, | | |
| 677 | }; | | |
| 678 | } | | |
| 679 | | | |
| 680 | while (true) { | 774 | while (true) { |
| 681 | _ = wait(io, global_progress.refresh_rate_ns); | 775 | writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) { |
| 682 | | 776 | error.WriteFailed => return fw.err.?, |
| 683 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) | | |
| 684 | return; | | |
| 685 | | | |
| 686 | const serialized = serialize(&serialized_buffer); | | |
| 687 | writeIpc(io, file, serialized) catch |err| switch (err) { | | |
| 688 | error.BrokenPipe => return, | | |
| 689 | }; | 777 | }; |
| | 778 | |
| | 779 | _ = try io.sleep(.fromNanoseconds(global_progress.refresh_rate_ns), .awake); |
| 690 | } | 780 | } |
| 691 | } | 781 | } |
| 692 | | 782 | |
| ... | @@ -865,31 +955,49 @@ const Serialized = struct { | ... | @@ -865,31 +955,49 @@ const Serialized = struct { |
| 865 | const Buffer = struct { | 955 | const Buffer = struct { |
| 866 | parents: [node_storage_buffer_len]Node.Parent, | 956 | parents: [node_storage_buffer_len]Node.Parent, |
| 867 | storage: [node_storage_buffer_len]Node.Storage, | 957 | storage: [node_storage_buffer_len]Node.Storage, |
| 868 | map: [node_storage_buffer_len]Node.OptionalIndex, | | |
| 869 | | 958 | |
| 870 | parents_copy: [node_storage_buffer_len]Node.Parent, | 959 | ipc_start: u8, |
| 871 | storage_copy: [node_storage_buffer_len]Node.Storage, | 960 | ipc_end: u8, |
| 872 | ipc_metadata_fds_copy: [node_storage_buffer_len]Fd, | 961 | ipc_data: [ipc_storage_buffer_len]Ipc.Data, |
| 873 | ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata, | 962 | ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8, |
| 874 | | 963 | ipc_vecs: [ipc_storage_buffer_len][1][]u8, |
| 875 | ipc_metadata_fds: [node_storage_buffer_len]Fd, | 964 | batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage, |
| 876 | ipc_metadata: [node_storage_buffer_len]SavedMetadata, | 965 | batch: Io.Batch, |
| | 966 | |
| | 967 | fn init(buffer: *Buffer) void { |
| | 968 | buffer.ipc_start = 0; |
| | 969 | buffer.ipc_end = 0; |
| | 970 | @memset(&buffer.ipc_data, .unused); |
| | 971 | buffer.batch = .init(&buffer.batch_storage); |
| | 972 | } |
| 877 | }; | 973 | }; |
| 878 | }; | 974 | }; |
| 879 | | 975 | |
| 880 | fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { | 976 | fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized { |
| 881 | var serialized_len: usize = 0; | 977 | var prev_parents: [node_storage_buffer_len]Node.Parent = undefined; |
| 882 | var any_ipc = false; | 978 | var prev_storage: [node_storage_buffer_len]Node.Storage = undefined; |
| | 979 | { |
| | 980 | const ipc_start = serialized_buffer.ipc_start; |
| | 981 | const ipc_end = serialized_buffer.ipc_end; |
| | 982 | @memcpy(prev_parents[ipc_start..ipc_end], serialized_buffer.parents[ipc_start..ipc_end]); |
| | 983 | @memcpy(prev_storage[ipc_start..ipc_end], serialized_buffer.storage[ipc_start..ipc_end]); |
| | 984 | } |
| 883 | | 985 | |
| 884 | // Iterate all of the nodes and construct a serializable copy of the state that can be examined | 986 | // Iterate all of the nodes and construct a serializable copy of the state that can be examined |
| 885 | // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the | 987 | // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the |
| 886 | // node count sometimes. | 988 | // node count sometimes. |
| 887 | const end_index = @min(@atomicLoad(u32, &global_progress.node_end_index, .monotonic), global_progress.node_storage.len); | 989 | const end_index = @min( |
| | 990 | @atomicLoad(u32, &global_progress.node_end_index, .monotonic), |
| | 991 | node_storage_buffer_len, |
| | 992 | ); |
| | 993 | var map: [node_storage_buffer_len]Node.OptionalIndex = undefined; |
| | 994 | var serialized_len: u8 = 0; |
| | 995 | var maybe_ipc_start: ?u8 = null; |
| 888 | for ( | 996 | for ( |
| 889 | global_progress.node_parents[0..end_index], | 997 | global_progress.node_parents[0..end_index], |
| 890 | global_progress.node_storage[0..end_index], | 998 | global_progress.node_storage[0..end_index], |
| 891 | serialized_buffer.map[0..end_index], | 999 | map[0..end_index], |
| 892 | ) |*parent_ptr, *storage_ptr, *map| { | 1000 | ) |*parent_ptr, *storage_ptr, *map_entry| { |
| 893 | const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic); | 1001 | const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic); |
| 894 | if (parent == .unused) { | 1002 | if (parent == .unused) { |
| 895 | // We might read "mixed" node data in this loop, due to weird atomic things | 1003 | // We might read "mixed" node data in this loop, due to weird atomic things |
| ... | @@ -903,17 +1011,17 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { | ... | @@ -903,17 +1011,17 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { |
| 903 | // parent, it will just not be printed at all. The general idea here is that performance | 1011 | // parent, it will just not be printed at all. The general idea here is that performance |
| 904 | // is more important than 100% correct output every frame, given that this API is likely | 1012 | // is more important than 100% correct output every frame, given that this API is likely |
| 905 | // to be used in hot paths! | 1013 | // to be used in hot paths! |
| 906 | map.* = .none; | 1014 | map_entry.* = .none; |
| 907 | continue; | 1015 | continue; |
| 908 | } | 1016 | } |
| 909 | const dest_storage = &serialized_buffer.storage[serialized_len]; | 1017 | const dest_storage = &serialized_buffer.storage[serialized_len]; |
| 910 | copyAtomicLoad(&dest_storage.name, &storage_ptr.name); | 1018 | copyAtomicLoad(&dest_storage.name, &storage_ptr.name); |
| 911 | dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcFd` | 1019 | dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcIndex` |
| 912 | dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic); | 1020 | dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic); |
| 913 | | 1021 | |
| 914 | any_ipc = any_ipc or (dest_storage.getIpcFd() != null); | | |
| 915 | serialized_buffer.parents[serialized_len] = parent; | 1022 | serialized_buffer.parents[serialized_len] = parent; |
| 916 | map.* = @enumFromInt(serialized_len); | 1023 | map_entry.* = @enumFromInt(serialized_len); |
| | 1024 | if (maybe_ipc_start == null and dest_storage.getIpcIndex() != null) maybe_ipc_start = serialized_len; |
| 917 | serialized_len += 1; | 1025 | serialized_len += 1; |
| 918 | } | 1026 | } |
| 919 | | 1027 | |
| ... | @@ -922,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { | ... | @@ -922,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { |
| 922 | parent.* = switch (parent.*) { | 1030 | parent.* = switch (parent.*) { |
| 923 | .unused => unreachable, | 1031 | .unused => unreachable, |
| 924 | .none => .none, | 1032 | .none => .none, |
| 925 | _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(), | 1033 | _ => |p| map[@intFromEnum(p)].toParent(), |
| 926 | }; | 1034 | }; |
| 927 | } | 1035 | } |
| 928 | | 1036 | |
| 929 | // Find nodes which correspond to child processes. | 1037 | // Fill pipe buffers. |
| 930 | if (any_ipc) | 1038 | const batch = &serialized_buffer.batch; |
| 931 | serialized_len = serializeIpc(serialized_len, serialized_buffer); | 1039 | batch.awaitConcurrent(io, .{ |
| 932 | | 1040 | .duration = .{ .raw = .zero, .clock = .awake }, |
| 933 | return .{ | 1041 | }) catch |err| switch (err) { |
| 934 | .parents = serialized_buffer.parents[0..serialized_len], | 1042 | error.Timeout => {}, |
| 935 | .storage = serialized_buffer.storage[0..serialized_len], | 1043 | else => |e| return e, |
| | 1044 | }; |
| | 1045 | var ready_len: u8 = 0; |
| | 1046 | while (batch.next()) |operation| switch (operation.index) { |
| | 1047 | 0...ipc_storage_buffer_len - 1 => { |
| | 1048 | const ipc_data = &serialized_buffer.ipc_data[operation.index]; |
| | 1049 | ipc_data.bytes_read += @intCast( |
| | 1050 | operation.result.file_read_streaming catch |err| switch (err) { |
| | 1051 | error.EndOfStream => { |
| | 1052 | const file = global_progress.ipc_files[operation.index]; |
| | 1053 | const ipc = @atomicRmw( |
| | 1054 | Ipc, |
| | 1055 | &global_progress.ipc[operation.index], |
| | 1056 | .And, |
| | 1057 | .{ |
| | 1058 | .locked = false, |
| | 1059 | .valid = true, |
| | 1060 | .generation = std.math.maxInt(Ipc.Generation), |
| | 1061 | }, |
| | 1062 | .release, |
| | 1063 | ); |
| | 1064 | assert(ipc.locked); |
| | 1065 | if (!ipc.valid) file.close(io); |
| | 1066 | ipc_data.* = .unused; |
| | 1067 | continue; |
| | 1068 | }, |
| | 1069 | else => |e| return e, |
| | 1070 | }, |
| | 1071 | ); |
| | 1072 | assert(ipc_data.state == .pending); |
| | 1073 | ipc_data.state = .ready; |
| | 1074 | ready_len += 1; |
| | 1075 | }, |
| | 1076 | else => unreachable, |
| 936 | }; | 1077 | }; |
| 937 | } | | |
| 938 | | | |
| 939 | const SavedMetadata = struct { | | |
| 940 | remaining_read_trash_bytes: u16, | | |
| 941 | main_index: u8, | | |
| 942 | start_index: u8, | | |
| 943 | nodes_len: u8, | | |
| 944 | }; | | |
| 945 | | | |
| 946 | const Fd = enum(i32) { | | |
| 947 | _, | | |
| 948 | | | |
| 949 | fn init(fd: Io.File.Handle) Fd { | | |
| 950 | return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd); | | |
| 951 | } | | |
| 952 | | | |
| 953 | fn get(fd: Fd) Io.File.Handle { | | |
| 954 | return if (is_windows) | | |
| 955 | @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd))))) | | |
| 956 | else | | |
| 957 | @intFromEnum(fd); | | |
| 958 | } | | |
| 959 | }; | | |
| 960 | | | |
| 961 | var ipc_metadata_len: u8 = 0; | | |
| 962 | | | |
| 963 | fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize { | | |
| 964 | const io = global_progress.io; | | |
| 965 | const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy; | | |
| 966 | const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy; | | |
| 967 | const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; | | |
| 968 | const ipc_metadata = &serialized_buffer.ipc_metadata; | | |
| 969 | | | |
| 970 | var serialized_len = start_serialized_len; | | |
| 971 | var pipe_buf: [2 * 4096]u8 = undefined; | | |
| 972 | | | |
| 973 | const old_ipc_metadata_fds = ipc_metadata_fds_copy[0..ipc_metadata_len]; | | |
| 974 | const old_ipc_metadata = ipc_metadata_copy[0..ipc_metadata_len]; | | |
| 975 | ipc_metadata_len = 0; | | |
| 976 | | 1078 | |
| 977 | main_loop: for ( | 1079 | // Find nodes which correspond to child processes. |
| 978 | serialized_buffer.parents[0..serialized_len], | 1080 | const ipc_start = maybe_ipc_start orelse serialized_len; |
| 979 | serialized_buffer.storage[0..serialized_len], | 1081 | serialized_buffer.ipc_start = ipc_start; |
| 980 | 0.., | 1082 | for ( |
| | 1083 | serialized_buffer.parents[ipc_start..serialized_len], |
| | 1084 | serialized_buffer.storage[ipc_start..serialized_len], |
| | 1085 | ipc_start.., |
| 981 | ) |main_parent, *main_storage, main_index| { | 1086 | ) |main_parent, *main_storage, main_index| { |
| 982 | if (main_parent == .unused) continue; | 1087 | if (main_parent == .unused) continue; |
| 983 | const file: Io.File = .{ | 1088 | const ipc_index = main_storage.getIpcIndex() orelse continue; |
| 984 | .handle = main_storage.getIpcFd() orelse continue, | 1089 | const ipc = &global_progress.ipc[ipc_index.slot]; |
| 985 | .flags = .{ .nonblocking = true }, | 1090 | const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot]; |
| 986 | }; | 1091 | state: switch (ipc_data.state) { |
| 987 | const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); | 1092 | .unused => { |
| 988 | var bytes_read: usize = 0; | 1093 | if (@cmpxchgWeak( |
| 989 | while (true) { | 1094 | Ipc, |
| 990 | const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) { | 1095 | ipc, |
| 991 | error.WouldBlock, error.EndOfStream => break, | 1096 | .{ .locked = false, .valid = true, .generation = ipc_index.generation }, |
| 992 | else => |e| { | 1097 | .{ .locked = true, .valid = true, .generation = ipc_index.generation }, |
| 993 | std.log.debug("failed to read child progress data: {t}", .{e}); | 1098 | .acquire, |
| 994 | main_storage.completed_count = 0; | 1099 | .monotonic, |
| 995 | main_storage.estimated_total_count = 0; | 1100 | )) |_| continue; |
| 996 | continue :main_loop; | 1101 | |
| 997 | }, | 1102 | const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot]; |
| 998 | }; | 1103 | ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]}; |
| 999 | if (opt_saved_metadata) |m| { | 1104 | batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{ |
| 1000 | if (m.remaining_read_trash_bytes > 0) { | 1105 | .file = global_progress.ipc_files[ipc_index.slot], |
| 1001 | assert(bytes_read == 0); | 1106 | .data = ipc_vec, |
| 1002 | if (m.remaining_read_trash_bytes >= n) { | 1107 | } }); |
| 1003 | m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n); | 1108 | |
| 1004 | continue; | 1109 | ipc_data.* = .{ |
| 1005 | } | 1110 | .state = .pending, |
| 1006 | const src = pipe_buf[m.remaining_read_trash_bytes..n]; | 1111 | .bytes_read = 0, |
| 1007 | @memmove(pipe_buf[0..src.len], src); | 1112 | .main_index = @intCast(main_index), |
| 1008 | m.remaining_read_trash_bytes = 0; | 1113 | .start_index = serialized_len, |
| 1009 | bytes_read = src.len; | 1114 | .nodes_len = 0, |
| 1010 | continue; | 1115 | }; |
| 1011 | } | 1116 | main_storage.completed_count = 0; |
| 1012 | } | 1117 | main_storage.estimated_total_count = 0; |
| 1013 | bytes_read += n; | 1118 | }, |
| 1014 | } | 1119 | .pending => { |
| 1015 | // Ignore all but the last message on the pipe. | 1120 | const start_index = ipc_data.start_index; |
| 1016 | var input: []u8 = pipe_buf[0..bytes_read]; | 1121 | const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len); |
| 1017 | if (input.len == 0) { | 1122 | |
| 1018 | serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle); | 1123 | main_storage.copyRoot(&prev_storage[ipc_data.main_index]); |
| 1019 | continue; | 1124 | @memcpy( |
| 1020 | } | 1125 | serialized_buffer.storage[serialized_len..][0..nodes_len], |
| 1021 | | 1126 | prev_storage[start_index..][0..nodes_len], |
| 1022 | const storage, const parents = while (true) { | 1127 | ); |
| 1023 | const subtree_len: usize = input[0]; | 1128 | for ( |
| 1024 | const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent)); | 1129 | serialized_buffer.parents[serialized_len..][0..nodes_len], |
| 1025 | if (input.len < expected_bytes) { | 1130 | prev_parents[serialized_len..][0..nodes_len], |
| 1026 | // Ignore short reads. We'll handle the next full message when it comes instead. | 1131 | ) |*parent, prev_parent| parent.* = switch (prev_parent) { |
| 1027 | const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len); | 1132 | .none, .unused => .none, |
| 1028 | serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle); | 1133 | _ => if (@intFromEnum(prev_parent) == ipc_data.main_index) |
| 1029 | continue :main_loop; | 1134 | @enumFromInt(main_index) |
| 1030 | } | 1135 | else if (@intFromEnum(prev_parent) >= start_index and |
| 1031 | if (input.len > expected_bytes) { | 1136 | @intFromEnum(prev_parent) < start_index + nodes_len) |
| 1032 | input = input[expected_bytes..]; | 1137 | @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len) |
| 1033 | continue; | 1138 | else |
| 1034 | } | 1139 | .none, |
| 1035 | const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)]; | 1140 | }; |
| 1036 | const parents_bytes = input[1 + storage_bytes.len ..][0 .. subtree_len * @sizeOf(Node.Parent)]; | | |
| 1037 | break .{ | | |
| 1038 | std.mem.bytesAsSlice(Node.Storage, storage_bytes), | | |
| 1039 | std.mem.bytesAsSlice(Node.Parent, parents_bytes), | | |
| 1040 | }; | | |
| 1041 | }; | | |
| 1042 | | | |
| 1043 | const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len)); | | |
| 1044 | | 1141 | |
| 1045 | // Remember in case the pipe is empty on next update. | 1142 | ipc_data.main_index = @intCast(main_index); |
| 1046 | ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle); | 1143 | ipc_data.start_index = serialized_len; |
| 1047 | ipc_metadata[ipc_metadata_len] = .{ | 1144 | ipc_data.nodes_len = nodes_len; |
| 1048 | .remaining_read_trash_bytes = 0, | 1145 | serialized_len += nodes_len; |
| 1049 | .start_index = @intCast(serialized_len), | 1146 | }, |
| 1050 | .nodes_len = nodes_len, | 1147 | .ready => { |
| 1051 | .main_index = @intCast(main_index), | 1148 | const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot]; |
| 1052 | }; | 1149 | const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer); |
| 1053 | ipc_metadata_len += 1; | 1150 | const packet_is_empty = packet_end - packet_start <= 1; |
| 1054 | | 1151 | if (!packet_is_empty) { |
| 1055 | // Mount the root here. | 1152 | const storage, const parents, const nodes_len = packet_contents: { |
| 1056 | copyRoot(main_storage, &storage[0]); | 1153 | var packet_index: usize = packet_start; |
| 1057 | if (is_big_endian) main_storage.byteSwap(); | 1154 | const nodes_len: u16 = ipc_buffer[packet_index]; |
| 1058 | | 1155 | packet_index += 1; |
| 1059 | // Copy the rest of the tree to the end. | 1156 | const storage_bytes = |
| 1060 | const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len]; | 1157 | ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)]; |
| 1061 | @memcpy(storage_dest, storage[1..][0..nodes_len]); | 1158 | packet_index += storage_bytes.len; |
| 1062 | | 1159 | const parents_bytes = |
| 1063 | // Always little-endian over the pipe. | 1160 | ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)]; |
| 1064 | if (is_big_endian) for (storage_dest) |*s| s.byteSwap(); | 1161 | packet_index += parents_bytes.len; |
| 1065 | | 1162 | assert(packet_index == packet_end); |
| 1066 | // Patch up parent pointers taking into account how the subtree is mounted. | 1163 | const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes); |
| 1067 | for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| { | 1164 | const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes); |
| 1068 | dest.* = switch (p) { | 1165 | const children_nodes_len = |
| 1069 | // Fix bad data so the rest of the code does not see `unused`. | 1166 | @min(nodes_len - 1, node_storage_buffer_len - serialized_len); |
| 1070 | .none, .unused => .none, | 1167 | break :packet_contents .{ storage, parents, children_nodes_len }; |
| 1071 | // Root node is being mounted here. | 1168 | }; |
| 1072 | @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index), | 1169 | |
| 1073 | // Other nodes mounted at the end. | 1170 | // Mount the root here. |
| 1074 | // Don't trust child data; if the data is outside the expected range, ignore the data. | 1171 | main_storage.copyRoot(&storage[0]); |
| 1075 | // This also handles the case when data was truncated. | 1172 | if (is_big_endian) main_storage.byteSwap(); |
| 1076 | _ => |off| if (@intFromEnum(off) > nodes_len) | 1173 | |
| 1077 | .none | 1174 | // Copy the rest of the tree to the end. |
| 1078 | else | 1175 | const serialized_storage = |
| 1079 | @enumFromInt(serialized_len + @intFromEnum(off) - 1), | 1176 | serialized_buffer.storage[serialized_len..][0..nodes_len]; |
| 1080 | }; | 1177 | @memcpy(serialized_storage, storage[1..][0..nodes_len]); |
| | 1178 | if (is_big_endian) for (serialized_storage) |*s| s.byteSwap(); |
| | 1179 | |
| | 1180 | // Patch up parent pointers taking into account how the subtree is mounted. |
| | 1181 | for ( |
| | 1182 | serialized_buffer.parents[serialized_len..][0..nodes_len], |
| | 1183 | parents[1..][0..nodes_len], |
| | 1184 | ) |*parent, prev_parent| parent.* = switch (prev_parent) { |
| | 1185 | // Fix bad data so the rest of the code does not see `unused`. |
| | 1186 | .none, .unused => .none, |
| | 1187 | // Root node is being mounted here. |
| | 1188 | @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index), |
| | 1189 | // Other nodes mounted at the end. |
| | 1190 | // Don't trust child data; if the data is outside the expected range, |
| | 1191 | // ignore the data. This also handles the case when data was truncated. |
| | 1192 | _ => if (@intFromEnum(prev_parent) <= nodes_len) |
| | 1193 | @enumFromInt(@intFromEnum(prev_parent) - 1 + serialized_len) |
| | 1194 | else |
| | 1195 | .none, |
| | 1196 | }; |
| | 1197 | |
| | 1198 | ipc_data.main_index = @intCast(main_index); |
| | 1199 | ipc_data.start_index = serialized_len; |
| | 1200 | ipc_data.nodes_len = nodes_len; |
| | 1201 | serialized_len += nodes_len; |
| | 1202 | } |
| | 1203 | const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot]; |
| | 1204 | ipc_data.rebase(ipc_buffer, ipc_vec, batch, ipc_index.slot, packet_end); |
| | 1205 | ready_len -= 1; |
| | 1206 | if (packet_is_empty) continue :state .pending; |
| | 1207 | }, |
| 1081 | } | 1208 | } |
| 1082 | | | |
| 1083 | serialized_len += nodes_len; | | |
| 1084 | } | | |
| 1085 | | | |
| 1086 | // Save a copy in case any pipes are empty on the next update. | | |
| 1087 | @memcpy(serialized_buffer.parents_copy[0..serialized_len], serialized_buffer.parents[0..serialized_len]); | | |
| 1088 | @memcpy(serialized_buffer.storage_copy[0..serialized_len], serialized_buffer.storage[0..serialized_len]); | | |
| 1089 | @memcpy(ipc_metadata_fds_copy[0..ipc_metadata_len], ipc_metadata_fds[0..ipc_metadata_len]); | | |
| 1090 | @memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]); | | |
| 1091 | | | |
| 1092 | return serialized_len; | | |
| 1093 | } | | |
| 1094 | | | |
| 1095 | fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void { | | |
| 1096 | dest.* = .{ | | |
| 1097 | .completed_count = src.completed_count, | | |
| 1098 | .estimated_total_count = src.estimated_total_count, | | |
| 1099 | .name = if (src.name[0] == 0) dest.name else src.name, | | |
| 1100 | }; | | |
| 1101 | } | | |
| 1102 | | | |
| 1103 | fn findOld( | | |
| 1104 | ipc_fd: Io.File.Handle, | | |
| 1105 | old_metadata_fds: []Fd, | | |
| 1106 | old_metadata: []SavedMetadata, | | |
| 1107 | ) ?*SavedMetadata { | | |
| 1108 | for (old_metadata_fds, old_metadata) |fd, *m| { | | |
| 1109 | if (fd.get() == ipc_fd) | | |
| 1110 | return m; | | |
| 1111 | } | 1209 | } |
| 1112 | return null; | 1210 | serialized_buffer.ipc_end = serialized_len; |
| 1113 | } | 1211 | |
| 1114 | | 1212 | // Ignore data from unused pipes. This ensures that if a child process exists we will |
| 1115 | fn useSavedIpcData( | 1213 | // eventually see `EndOfStream` and close the pipe. |
| 1116 | start_serialized_len: usize, | 1214 | if (ready_len > 0) for ( |
| 1117 | serialized_buffer: *Serialized.Buffer, | 1215 | &serialized_buffer.ipc_data, |
| 1118 | main_storage: *Node.Storage, | 1216 | &serialized_buffer.ipc_buffers, |
| 1119 | main_index: usize, | 1217 | &serialized_buffer.ipc_vecs, |
| 1120 | opt_saved_metadata: ?*SavedMetadata, | 1218 | 0.., |
| 1121 | remaining_read_trash_bytes: u16, | 1219 | ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) { |
| 1122 | fd: Io.File.Handle, | 1220 | .unused, .pending => {}, |
| 1123 | ) usize { | 1221 | .ready => { |
| 1124 | const parents_copy = &serialized_buffer.parents_copy; | 1222 | _, const packet_end = ipc_data.findLastPacket(ipc_buffer); |
| 1125 | const storage_copy = &serialized_buffer.storage_copy; | 1223 | ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end); |
| 1126 | const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; | 1224 | ready_len -= 1; |
| 1127 | const ipc_metadata = &serialized_buffer.ipc_metadata; | 1225 | }, |
| 1128 | | | |
| 1129 | const saved_metadata = opt_saved_metadata orelse { | | |
| 1130 | main_storage.completed_count = 0; | | |
| 1131 | main_storage.estimated_total_count = 0; | | |
| 1132 | if (remaining_read_trash_bytes > 0) { | | |
| 1133 | ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd); | | |
| 1134 | ipc_metadata[ipc_metadata_len] = .{ | | |
| 1135 | .remaining_read_trash_bytes = remaining_read_trash_bytes, | | |
| 1136 | .start_index = @intCast(start_serialized_len), | | |
| 1137 | .nodes_len = 0, | | |
| 1138 | .main_index = @intCast(main_index), | | |
| 1139 | }; | | |
| 1140 | ipc_metadata_len += 1; | | |
| 1141 | } | | |
| 1142 | return start_serialized_len; | | |
| 1143 | }; | 1226 | }; |
| | 1227 | assert(ready_len == 0); |
| 1144 | | 1228 | |
| 1145 | const start_index = saved_metadata.start_index; | 1229 | return .{ |
| 1146 | const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len); | 1230 | .parents = serialized_buffer.parents[0..serialized_len], |
| 1147 | const old_main_index = saved_metadata.main_index; | 1231 | .storage = serialized_buffer.storage[0..serialized_len], |
| 1148 | | | |
| 1149 | ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd); | | |
| 1150 | ipc_metadata[ipc_metadata_len] = .{ | | |
| 1151 | .remaining_read_trash_bytes = remaining_read_trash_bytes, | | |
| 1152 | .start_index = @intCast(start_serialized_len), | | |
| 1153 | .nodes_len = nodes_len, | | |
| 1154 | .main_index = @intCast(main_index), | | |
| 1155 | }; | 1232 | }; |
| 1156 | ipc_metadata_len += 1; | | |
| 1157 | | | |
| 1158 | const parents = parents_copy[start_index..][0..nodes_len]; | | |
| 1159 | const storage = storage_copy[start_index..][0..nodes_len]; | | |
| 1160 | | | |
| 1161 | copyRoot(main_storage, &storage_copy[old_main_index]); | | |
| 1162 | | | |
| 1163 | @memcpy(serialized_buffer.storage[start_serialized_len..][0..storage.len], storage); | | |
| 1164 | | | |
| 1165 | for (serialized_buffer.parents[start_serialized_len..][0..parents.len], parents) |*dest, p| { | | |
| 1166 | dest.* = switch (p) { | | |
| 1167 | .none, .unused => .none, | | |
| 1168 | _ => |prev| d: { | | |
| 1169 | if (@intFromEnum(prev) == old_main_index) { | | |
| 1170 | break :d @enumFromInt(main_index); | | |
| 1171 | } else if (@intFromEnum(prev) > nodes_len) { | | |
| 1172 | break :d .none; | | |
| 1173 | } else { | | |
| 1174 | break :d @enumFromInt(@intFromEnum(prev) - start_index + start_serialized_len); | | |
| 1175 | } | | |
| 1176 | }, | | |
| 1177 | }; | | |
| 1178 | } | | |
| 1179 | | | |
| 1180 | return start_serialized_len + storage.len; | | |
| 1181 | } | 1233 | } |
| 1182 | | 1234 | |
| 1183 | fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } { | 1235 | fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } { |
| 1184 | const serialized = serialize(serialized_buffer); | 1236 | if (global_progress.rows == 0 or global_progress.cols == 0) return error.WindowTooSmall; |
| | 1237 | |
| | 1238 | const serialized = try serialize(io, serialized_buffer); |
| 1185 | | 1239 | |
| 1186 | // Now we can analyze our copy of the graph without atomics, reconstructing | 1240 | // Now we can analyze our copy of the graph without atomics, reconstructing |
| 1187 | // children lists which do not exist in the canonical data. These are | 1241 | // children lists which do not exist in the canonical data. These are |
| ... | @@ -1416,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { | ... | @@ -1416,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { |
| 1416 | return nl_n + 2 < p.rows; | 1470 | return nl_n + 2 < p.rows; |
| 1417 | } | 1471 | } |
| 1418 | | 1472 | |
| 1419 | var remaining_write_trash_bytes: usize = 0; | 1473 | fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void { |
| 1420 | | | |
| 1421 | fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { | | |
| 1422 | // Byteswap if necessary to ensure little endian over the pipe. This is | 1474 | // Byteswap if necessary to ensure little endian over the pipe. This is |
| 1423 | // needed because the parent or child process might be running in qemu. | 1475 | // needed because the parent or child process might be running in qemu. |
| 1424 | if (is_big_endian) for (serialized.storage) |*s| s.byteSwap(); | 1476 | if (is_big_endian) for (serialized.storage) |*s| s.byteSwap(); |
| ... | @@ -1429,62 +1481,8 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi | ... | @@ -1429,62 +1481,8 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi |
| 1429 | const storage = std.mem.sliceAsBytes(serialized.storage); | 1481 | const storage = std.mem.sliceAsBytes(serialized.storage); |
| 1430 | const parents = std.mem.sliceAsBytes(serialized.parents); | 1482 | const parents = std.mem.sliceAsBytes(serialized.parents); |
| 1431 | | 1483 | |
| 1432 | var vecs: [3][]const u8 = .{ header, storage, parents }; | 1484 | var vec = [3][]const u8{ header, storage, parents }; |
| 1433 | | 1485 | try writer.writeVecAll(&vec); |
| 1434 | // Ensures the packet can fit in the pipe buffer. | | |
| 1435 | const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) + | | |
| 1436 | node_storage_buffer_len * @sizeOf(Node.OptionalIndex); | | |
| 1437 | comptime assert(upper_bound_msg_len <= 4096); | | |
| 1438 | | | |
| 1439 | while (remaining_write_trash_bytes > 0) { | | |
| 1440 | // We do this in a separate write call to give a better chance for the | | |
| 1441 | // writev below to be in a single packet. | | |
| 1442 | const n = @min(parents.len, remaining_write_trash_bytes); | | |
| 1443 | if (file.writeStreaming(io, &.{}, &.{parents[0..n]}, 1)) |written| { | | |
| 1444 | remaining_write_trash_bytes -= written; | | |
| 1445 | continue; | | |
| 1446 | } else |err| switch (err) { | | |
| 1447 | error.WouldBlock => return, | | |
| 1448 | error.BrokenPipe => return error.BrokenPipe, | | |
| 1449 | else => |e| { | | |
| 1450 | std.log.debug("failed to send progress to parent process: {t}", .{e}); | | |
| 1451 | return error.BrokenPipe; | | |
| 1452 | }, | | |
| 1453 | } | | |
| 1454 | } | | |
| 1455 | | | |
| 1456 | // If this write would block we do not want to keep trying, but we need to | | |
| 1457 | // know if a partial message was written. | | |
| 1458 | if (writevNonblock(io, file, &vecs)) |written| { | | |
| 1459 | const total = header.len + storage.len + parents.len; | | |
| 1460 | if (written < total) { | | |
| 1461 | remaining_write_trash_bytes = total - written; | | |
| 1462 | } | | |
| 1463 | } else |err| switch (err) { | | |
| 1464 | error.WouldBlock => {}, | | |
| 1465 | error.BrokenPipe => return error.BrokenPipe, | | |
| 1466 | else => |e| { | | |
| 1467 | std.log.debug("failed to send progress to parent process: {t}", .{e}); | | |
| 1468 | return error.BrokenPipe; | | |
| 1469 | }, | | |
| 1470 | } | | |
| 1471 | } | | |
| 1472 | | | |
| 1473 | fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize { | | |
| 1474 | var iov_index: usize = 0; | | |
| 1475 | var written: usize = 0; | | |
| 1476 | var total_written: usize = 0; | | |
| 1477 | while (true) { | | |
| 1478 | while (if (iov_index < iov.len) | | |
| 1479 | written >= iov[iov_index].len | | |
| 1480 | else | | |
| 1481 | return total_written) : (iov_index += 1) written -= iov[iov_index].len; | | |
| 1482 | iov[iov_index].ptr += written; | | |
| 1483 | iov[iov_index].len -= written; | | |
| 1484 | written = try file.writeStreaming(io, &.{}, iov, 1); | | |
| 1485 | if (written == 0) return total_written; | | |
| 1486 | total_written += written; | | |
| 1487 | } | | |
| 1488 | } | 1486 | } |
| 1489 | | 1487 | |
| 1490 | fn maybeUpdateSize(io: Io, resize_flag: bool) !void { | 1488 | fn maybeUpdateSize(io: Io, resize_flag: bool) !void { |