authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-01-31 20:22:53-05:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-02-04 15:20:10-05:00
log71156aff806856d5d48e72cd8aeb9315b9ae0b62
treef312dceabcfece3ebf7680b7dc6d8a1d286a2fb5
parentffc6da29e3fa53a9c81bcb8af7467cdd6345538f

std.Progress: implement ipc resource cleanup


4 files changed, 554 insertions(+), 541 deletions(-)

lib/std/Build/Step.zig+20-32
...@@ -386,10 +386,14 @@ pub const ZigProcess = struct {...@@ -386,10 +386,14 @@ pub const ZigProcess = struct {
386 child: std.process.Child,386 child: std.process.Child,
387 multi_reader_buffer: Io.File.MultiReader.Buffer(2),387 multi_reader_buffer: Io.File.MultiReader.Buffer(2),
388 multi_reader: Io.File.MultiReader,388 multi_reader: Io.File.MultiReader,
389 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,389 progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn,
390390
391 pub const StreamEnum = enum { stdout, stderr };391 pub const StreamEnum = enum { stdout, stderr };
392392
393 pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void {
394 zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null;
395 }
396
393 pub fn deinit(zp: *ZigProcess, io: Io) void {397 pub fn deinit(zp: *ZigProcess, io: Io) void {
394 zp.child.kill(io);398 zp.child.kill(io);
395 zp.multi_reader.deinit();399 zp.multi_reader.deinit();
...@@ -417,7 +421,14 @@ pub fn evalZigProcess(...@@ -417,7 +421,14 @@ pub fn evalZigProcess(
417421
418 if (s.getZigProcess()) |zp| update: {422 if (s.getZigProcess()) |zp| update: {
419 assert(watch);423 assert(watch);
420 if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);424 if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index);
425 zp.progress_ipc_index = null;
426 var exited = false;
427 defer if (exited) {
428 s.cast(Compile).?.zig_process = null;
429 zp.deinit(io);
430 gpa.destroy(zp);
431 } else zp.saveState(prog_node);
421 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {432 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
422 error.BrokenPipe, error.EndOfStream => |reason| {433 error.BrokenPipe, error.EndOfStream => |reason| {
423 std.log.info("{s} restart required: {t}", .{ argv[0], reason });434 std.log.info("{s} restart required: {t}", .{ argv[0], reason });
...@@ -426,7 +437,7 @@ pub fn evalZigProcess(...@@ -426,7 +437,7 @@ pub fn evalZigProcess(
426 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });437 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
427 };438 };
428 _ = term;439 _ = term;
429 s.clearZigProcess(gpa);440 exited = true;
430 break :update;441 break :update;
431 },442 },
432 else => |e| return e,443 else => |e| return e,
...@@ -442,7 +453,7 @@ pub fn evalZigProcess(...@@ -442,7 +453,7 @@ pub fn evalZigProcess(
442 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });453 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
443 };454 };
444 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;455 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
445 s.clearZigProcess(gpa);456 exited = true;
446 try handleChildProcessTerm(s, term);457 try handleChildProcessTerm(s, term);
447 return error.MakeFailed;458 return error.MakeFailed;
448 }459 }
...@@ -467,19 +478,16 @@ pub fn evalZigProcess(...@@ -467,19 +478,16 @@ pub fn evalZigProcess(
467 .progress_node = prog_node,478 .progress_node = prog_node,
468 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });479 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
469480
470 zp.* = .{
471 .child = zp.child,
472 .multi_reader_buffer = undefined,
473 .multi_reader = undefined,
474 .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},
475 };
476 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{481 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
477 zp.child.stdout.?, zp.child.stderr.?,482 zp.child.stdout.?, zp.child.stderr.?,
478 });483 });
479 if (watch) s.setZigProcess(zp);484 if (watch) s.cast(Compile).?.zig_process = zp;
480 defer if (!watch) zp.deinit(io);485 defer if (!watch) zp.deinit(io);
481486
482 const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);487 const result = result: {
488 defer if (watch) zp.saveState(prog_node);
489 break :result try zigProcessUpdate(s, zp, watch, web_server, gpa);
490 };
483491
484 if (!watch) {492 if (!watch) {
485 // Send EOF to stdin.493 // Send EOF to stdin.
...@@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {...@@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {
670 };678 };
671}679}
672680
673fn setZigProcess(s: *Step, zp: *ZigProcess) void {
674 switch (s.id) {
675 .compile => s.cast(Compile).?.zig_process = zp,
676 else => unreachable,
677 }
678}
679
680fn clearZigProcess(s: *Step, gpa: Allocator) void {
681 switch (s.id) {
682 .compile => {
683 const compile = s.cast(Compile).?;
684 if (compile.zig_process) |zp| {
685 gpa.destroy(zp);
686 compile.zig_process = null;
687 }
688 },
689 else => unreachable,
690 }
691}
692
693fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {681fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
694 const header: std.zig.Client.Message.Header = .{682 const header: std.zig.Client.Message.Header = .{
695 .tag = tag,683 .tag = tag,
lib/std/Io/Threaded.zig+50-41
...@@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;...@@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;
19const assert = std.debug.assert;19const assert = std.debug.assert;
20const posix = std.posix;20const posix = std.posix;
21const windows = std.os.windows;21const windows = std.os.windows;
22const ws2_32 = std.os.windows.ws2_32;22const ws2_32 = windows.ws2_32;
2323
24/// Thread-safe.24/// Thread-safe.
25///25///
...@@ -2609,8 +2609,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {...@@ -2609,8 +2609,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
2609 // opportunity to find additional ready operations.2609 // opportunity to find additional ready operations.
2610 break :t 0;2610 break :t 0;
2611 }2611 }
2612 const max_poll_ms = std.math.maxInt(i32);2612 break :t std.math.maxInt(i32);
2613 break :t max_poll_ms;
2614 };2613 };
2615 const syscall = try Syscall.start();2614 const syscall = try Syscall.start();
2616 const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms);2615 const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms);
...@@ -2730,6 +2729,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout...@@ -2730,6 +2729,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
2730 break :allocation allocation;2729 break :allocation allocation;
2731 };2730 };
2732 @memcpy(slice[0..poll_buffer_len], storage.slice);2731 @memcpy(slice[0..poll_buffer_len], storage.slice);
2732 storage.slice = slice;
2733 }2733 }
2734 storage.slice[len] = .{2734 storage.slice[len] = .{
2735 .fd = file.handle,2735 .fd = file.handle,
...@@ -2783,9 +2783,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout...@@ -2783,9 +2783,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
2783 }2783 }
2784 const d = deadline orelse break :t -1;2784 const d = deadline orelse break :t -1;
2785 const duration = d.durationFromNow(t_io);2785 const duration = d.durationFromNow(t_io);
2786 if (duration.raw.nanoseconds <= 0) return error.Timeout;2786 break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32));
2787 const max_poll_ms = std.math.maxInt(i32);
2788 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
2789 };2787 };
2790 const syscall = try Syscall.start();2788 const syscall = try Syscall.start();
2791 const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);2789 const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);
...@@ -14420,7 +14418,10 @@ const WindowsEnvironStrings = struct {...@@ -14420,7 +14418,10 @@ const WindowsEnvironStrings = struct {
14420 PATHEXT: ?[:0]const u16 = null,14418 PATHEXT: ?[:0]const u16 = null,
1442114419
14422 fn scan() WindowsEnvironStrings {14420 fn scan() WindowsEnvironStrings {
14423 const ptr = windows.peb().ProcessParameters.Environment;14421 const peb = windows.peb();
14422 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
14423 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
14424 const ptr = peb.ProcessParameters.Environment;
1442414425
14425 var result: WindowsEnvironStrings = .{};14426 var result: WindowsEnvironStrings = .{};
14426 var i: usize = 0;14427 var i: usize = 0;
...@@ -14446,7 +14447,7 @@ const WindowsEnvironStrings = struct {...@@ -14446,7 +14447,7 @@ const WindowsEnvironStrings = struct {
1444614447
14447 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {14448 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
14448 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);14449 const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);
14449 if (std.os.windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;14450 if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;
14450 }14451 }
14451 }14452 }
1445214453
...@@ -14465,29 +14466,46 @@ fn scanEnviron(t: *Threaded) void {...@@ -14465,29 +14466,46 @@ fn scanEnviron(t: *Threaded) void {
14465 // This value expires with any call that modifies the environment,14466 // This value expires with any call that modifies the environment,
14466 // which is outside of this Io implementation's control, so references14467 // which is outside of this Io implementation's control, so references
14467 // must be short-lived.14468 // must be short-lived.
14468 const ptr = windows.peb().ProcessParameters.Environment;14469 const peb = windows.peb();
14470 assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
14471 defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
14472 const ptr = peb.ProcessParameters.Environment;
1446914473
14470 var i: usize = 0;14474 var i: usize = 0;
14471 while (ptr[i] != 0) {14475 while (ptr[i] != 0) {
14472 const key_start = i;
1447314476
14474 // There are some special environment variables that start with =,14477 // There are some special environment variables that start with =,
14475 // so we need a special case to not treat = as a key/value separator14478 // so we need a special case to not treat = as a key/value separator
14476 // if it's the first character.14479 // if it's the first character.
14477 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=1413314480 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
14478 if (ptr[key_start] == '=') i += 1;14481 const key_start = i;
1447914482 if (ptr[i] == '=') i += 1;
14480 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}14483 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
14481 const key_w = ptr[key_start..i];14484 const key_w = ptr[key_start..i];
14482 if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {14485
14486 const value_start = i + 1;
14487 while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
14488 const value_w = ptr[value_start..i];
14489 i += 1; // skip over null byte
14490
14491 if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
14483 t.environ.exist.NO_COLOR = true;14492 t.environ.exist.NO_COLOR = true;
14484 } else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {14493 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
14485 t.environ.exist.CLICOLOR_FORCE = true;14494 t.environ.exist.CLICOLOR_FORCE = true;
14495 } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) {
14496 t.environ.zig_progress_file = file: {
14497 var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
14498 const len = std.unicode.calcWtf8Len(value_w);
14499 if (len > value_buf.len) break :file error.UnrecognizedFormat;
14500 assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len);
14501 break :file .{
14502 .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch
14503 break :file error.UnrecognizedFormat),
14504 .flags = .{ .nonblocking = true },
14505 };
14506 };
14486 }14507 }
14487 comptime assert(@sizeOf(Environ.String) == 0);14508 comptime assert(@sizeOf(Environ.String) == 0);
14488
14489 while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
14490 i += 1; // skip over null byte
14491 }14509 }
14492 } else if (native_os == .wasi and !builtin.link_libc) {14510 } else if (native_os == .wasi and !builtin.link_libc) {
14493 var environ_count: usize = undefined;14511 var environ_count: usize = undefined;
...@@ -14549,20 +14567,9 @@ fn scanEnviron(t: *Threaded) void {...@@ -14549,20 +14567,9 @@ fn scanEnviron(t: *Threaded) void {
14549 t.environ.exist.CLICOLOR_FORCE = true;14567 t.environ.exist.CLICOLOR_FORCE = true;
14550 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {14568 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
14551 t.environ.zig_progress_file = file: {14569 t.environ.zig_progress_file = file: {
14552 const int = std.fmt.parseInt(switch (@typeInfo(File.Handle)) {
14553 .int => |int_info| @Int(
14554 .unsigned,
14555 int_info.bits - @intFromBool(int_info.signedness == .signed),
14556 ),
14557 .pointer => usize,
14558 else => break :file error.UnsupportedOperation,
14559 }, value, 10) catch break :file error.UnrecognizedFormat;
14560 break :file .{14570 break :file .{
14561 .handle = switch (@typeInfo(File.Handle)) {14571 .handle = std.fmt.parseInt(u31, value, 10) catch
14562 .int => int,14572 break :file error.UnrecognizedFormat,
14563 .pointer => @ptrFromInt(int),
14564 else => comptime unreachable,
14565 },
14566 .flags = .{ .nonblocking = true },14573 .flags = .{ .nonblocking = true },
14567 };14574 };
14568 };14575 };
...@@ -14668,16 +14675,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14668,16 +14675,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14668 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);14675 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
14669 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;14676 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
1467014677
14671 const prog_pipe: [2]posix.fd_t = p: {14678 const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none)
14672 if (options.progress_node.index == .none) {14679 // We use CLOEXEC for the same reason as in `pipe_flags`.
14673 break :p .{ -1, -1 };14680 try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true })
14674 } else {14681 else
14675 // We use CLOEXEC for the same reason as in `pipe_flags`.14682 .{ -1, -1 };
14676 break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
14677 }
14678 };
14679 errdefer destroyPipe(prog_pipe);14683 errdefer destroyPipe(prog_pipe);
1468014684
14685 if (native_os == .linux and prog_pipe[0] != -1) {
14686 _ = posix.system.fcntl(prog_pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2));
14687 }
14688
14681 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);14689 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
14682 defer arena_allocator.deinit();14690 defer arena_allocator.deinit();
14683 const arena = arena_allocator.allocator();14691 const arena = arena_allocator.allocator();
...@@ -14801,7 +14809,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14801,7 +14809,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14801 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);14809 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
1480214810
14803 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);14811 if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
14804 options.progress_node.setIpcFd(prog_pipe[0]);14812 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
1480514813
14806 return .{14814 return .{
14807 .pid = pid,14815 .pid = pid,
...@@ -15259,8 +15267,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15259,8 +15267,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1525915267
15260 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{15268 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
15261 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },15269 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15262 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },15270 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } },
15263 .inbound = true,15271 .inbound = true,
15272 .quota = std.Progress.max_packet_len * 2,
15264 }) else undefined;15273 }) else undefined;
15265 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);15274 errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
1526615275
...@@ -15476,7 +15485,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15476,7 +15485,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1547615485
15477 if (options.progress_node.index != .none) {15486 if (options.progress_node.index != .none) {
15478 windows.CloseHandle(prog_pipe[1]);15487 windows.CloseHandle(prog_pipe[1]);
15479 options.progress_node.setIpcFd(prog_pipe[0]);15488 options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
15480 }15489 }
1548115490
15482 return .{15491 return .{
lib/std/Progress.zig+466-468
...@@ -11,7 +11,7 @@ const windows = std.os.windows;...@@ -11,7 +11,7 @@ const windows = std.os.windows;
11const testing = std.testing;11const testing = std.testing;
12const assert = std.debug.assert;12const assert = std.debug.assert;
13const posix = std.posix;13const posix = std.posix;
14const Writer = std.Io.Writer;14const Writer = Io.Writer;
1515
16/// Currently this API only supports this value being set to stderr, which16/// 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,
2121
22terminal_mode: TerminalMode,22terminal_mode: TerminalMode,
2323
24update_worker: ?Io.Future(void),24update_worker: ?Io.Future(WorkerError!void),
2525
26/// Atomically set by SIGWINCH as well as the root done() function.26/// Atomically set by SIGWINCH as well as the root done() function.
27redraw_event: Io.Event,27redraw_event: Io.Event,
28/// Indicates a request to shut down and reset global state.
29/// Accessed atomically.
30done: bool,
31need_clear: bool,28need_clear: bool,
32status: Status,29status: Status,
3330
...@@ -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 so40/// 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 the41/// that it can be iterated over efficiently without trashing too much of the
45/// CPU cache.42/// CPU cache.
46node_parents: []Node.Parent,43node_parents: [node_storage_buffer_len]Node.Parent,
47node_storage: []Node.Storage,44node_storage: [node_storage_buffer_len]Node.Storage,
48node_freelist_next: []Node.OptionalIndex,45node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex,
49node_freelist: Freelist,46node_freelist: Freelist,
50/// This is the number of elements in node arrays which have been used so far. Nodes before this47/// 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. This48/// 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.
53node_end_index: u32,50node_end_index: u32,
5451
52ipc_next: Ipc.SlotAtomic,
53ipc: [ipc_storage_buffer_len]Ipc,
54ipc_files: [ipc_storage_buffer_len]Io.File,
55
55start_failure: StartFailure,56start_failure: StartFailure,
5657
57pub const Status = enum {58pub 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};
7980
81pub 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
80pub const TerminalMode = union(enum) {155pub 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 {
116191
117 pub const none: Node = .{ .index = .none };192 pub const none: Node = .{ .index = .none };
118193
119 pub const max_name_len = 40;194 pub const max_name_len = 120;
120195
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)),
128203
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 }
137208
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 that211 // `estimated_total_count` max int indicates the special state that
146 // causes `completed_count` to be treated as a file descriptor, so212 // 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 }
151217
...@@ -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 }
157223
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 }
243317
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 }
298372
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 }
305381
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);
317394
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 }
341432
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 }
359460
360 /// Posix-only. Thread-safe. Assumes the node is storing an IPC file461 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 }
372472
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 {
387487
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,
421521
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};
428534
429pub const StartFailure = union(enum) {535pub 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};
435541
436const node_storage_buffer_len = 83;542/// One less than a power of two ensures `max_packet_len` is already a power of two.
437var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;543const node_storage_buffer_len = ipc_storage_buffer_len - 1;
438var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;544
439var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined;545/// Power of two to avoid wasted `ipc_next` increments.
546const ipc_storage_buffer_len = 128;
547
548pub const max_packet_len = std.math.ceilPowerOfTwoAssert(
549 usize,
550 1 + node_storage_buffer_len * (@sizeOf(Node.Storage) + @sizeOf(Node.OptionalIndex)),
551);
440552
441var default_draw_buffer: [4096]u8 = undefined;553var default_draw_buffer: [4096]u8 = undefined;
442554
443var debug_start_trace = std.debug.Trace.init;555var debug_start_trace = std.debug.Trace.init;
444556
445pub const have_ipc = switch (builtin.os.tag) {557pub const have_ipc = switch (builtin.os.tag) {
446 .wasi, .freestanding, .windows => false,558 .wasi, .freestanding => false,
447 else => true,559 else => true,
448};560};
449561
...@@ -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");
477589
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;
482594
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}
552664
553/// Returns whether a resize is needed to learn the terminal size.665/// Returns whether a resize is needed to learn the terminal size.
554fn wait(io: Io, timeout_ns: u64) bool {666fn 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}
565678
566fn updateTask(io: Io) void {679const WorkerError = error{WindowTooSmall} || Io.ConcurrentError || Io.Cancelable ||
680 Io.File.Writer.Error || Io.Operation.FileReadStreaming.Error;
681
682fn updateTask(io: Io) WorkerError!void {
567 // Store this data in the thread so that it does not need to be part of the683 // 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);
570688
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 a691 // We still want to go through the `Io` instance however in case it uses a
574 // task-switching mutex.692 // task-switching mutex.
575693
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);
580698 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);
591706 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}
608717
...@@ -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}
616725
617fn windowsApiUpdateTask(io: Io) void {726fn 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);
619732
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 a735 // We still want to go through the `Io` instance however in case it uses a
623 // task-switching mutex.736 // task-switching mutex.
624737
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);
629742 _ = 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}
662764
663fn ipcThreadRun(io: Io, file: Io.File) void {765fn 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 the766 // 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, &.{});
667772
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) {
682776 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}
692782
...@@ -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,
869958
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,
874963 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};
879975
880fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {976fn 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 }
883985
884 // Iterate all of the nodes and construct a serializable copy of the state that can be examined986 // 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 the987 // 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 things1003 // 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 performance1011 // 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 likely1012 // 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);
9131021
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 }
9191027
...@@ -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 }
9281036
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, .{
9321040 .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
939const SavedMetadata = struct {
940 remaining_read_trash_bytes: u16,
941 main_index: u8,
942 start_index: u8,
943 nodes_len: u8,
944};
945
946const 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
961var ipc_metadata_len: u8 = 0;
962
963fn 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;
9761078
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],
10211126 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));
10441141
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;
10541151 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];
10581155 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;
10621159 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;
10651162 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 .none1174 // Copy the rest of the tree to the end.
1078 else1175 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
1095fn 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
1103fn 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
11141212 // Ignore data from unused pipes. This ensures that if a child process exists we will
1115fn 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);
11441228
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}
11821234
1183fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } {1235fn 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);
11851239
1186 // Now we can analyze our copy of the graph without atomics, reconstructing1240 // Now we can analyze our copy of the graph without atomics, reconstructing
1187 // children lists which do not exist in the canonical data. These are1241 // 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}
14181472
1419var remaining_write_trash_bytes: usize = 0;1473fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void {
1420
1421fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {
1422 // Byteswap if necessary to ensure little endian over the pipe. This is1474 // 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);
14311483
1432 var vecs: [3][]const u8 = .{ header, storage, parents };1484 var vec = [3][]const u8{ header, storage, parents };
14331485 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
1473fn 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}
14891487
1490fn maybeUpdateSize(io: Io, resize_flag: bool) !void {1488fn maybeUpdateSize(io: Io, resize_flag: bool) !void {
lib/std/os/linux.zig+18
...@@ -1848,6 +1848,24 @@ pub const F = struct {...@@ -1848,6 +1848,24 @@ pub const F = struct {
1848 pub const RDLCK = if (is_sparc) 1 else 0;1848 pub const RDLCK = if (is_sparc) 1 else 0;
1849 pub const WRLCK = if (is_sparc) 2 else 1;1849 pub const WRLCK = if (is_sparc) 2 else 1;
1850 pub const UNLCK = if (is_sparc) 3 else 2;1850 pub const UNLCK = if (is_sparc) 3 else 2;
1851
1852 pub const LINUX_SPECIFIC_BASE = 1024;
1853
1854 pub const SETLEASE = LINUX_SPECIFIC_BASE + 0;
1855 pub const GETLEASE = LINUX_SPECIFIC_BASE + 1;
1856 pub const NOTIFY = LINUX_SPECIFIC_BASE + 2;
1857 pub const DUPFD_QUERY = LINUX_SPECIFIC_BASE + 3;
1858 pub const CREATED_QUERY = LINUX_SPECIFIC_BASE + 4;
1859 pub const CANCELLK = LINUX_SPECIFIC_BASE + 5;
1860 pub const DUPFD_CLOEXEC = LINUX_SPECIFIC_BASE + 6;
1861 pub const SETPIPE_SZ = LINUX_SPECIFIC_BASE + 7;
1862 pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8;
1863 pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9;
1864 pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10;
1865 pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11;
1866 pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12;
1867 pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13;
1868 pub const SET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 14;
1851};1869};
18521870
1853pub const F_OWNER = enum(i32) {1871pub const F_OWNER = enum(i32) {