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 {
386386 child: std.process.Child,
387387 multi_reader_buffer: Io.File.MultiReader.Buffer(2),
388388 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
391391 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
393397 pub fn deinit(zp: *ZigProcess, io: Io) void {
394398 zp.child.kill(io);
395399 zp.multi_reader.deinit();
......@@ -417,7 +421,14 @@ pub fn evalZigProcess(
417421
418422 if (s.getZigProcess()) |zp| update: {
419423 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);
421432 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
422433 error.BrokenPipe, error.EndOfStream => |reason| {
423434 std.log.info("{s} restart required: {t}", .{ argv[0], reason });
......@@ -426,7 +437,7 @@ pub fn evalZigProcess(
426437 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
427438 };
428439 _ = term;
429 s.clearZigProcess(gpa);
440 exited = true;
430441 break :update;
431442 },
432443 else => |e| return e,
......@@ -442,7 +453,7 @@ pub fn evalZigProcess(
442453 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
443454 };
444455 s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
445 s.clearZigProcess(gpa);
456 exited = true;
446457 try handleChildProcessTerm(s, term);
447458 return error.MakeFailed;
448459 }
......@@ -467,19 +478,16 @@ pub fn evalZigProcess(
467478 .progress_node = prog_node,
468479 }) 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 };
476481 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
477482 zp.child.stdout.?, zp.child.stderr.?,
478483 });
479 if (watch) s.setZigProcess(zp);
484 if (watch) s.cast(Compile).?.zig_process = zp;
480485 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
484492 if (!watch) {
485493 // Send EOF to stdin.
......@@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {
670678 };
671679}
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
693681fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
694682 const header: std.zig.Client.Message.Header = .{
695683 .tag = tag,
lib/std/Io/Threaded.zig+50-41
......@@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;
1919const assert = std.debug.assert;
2020const posix = std.posix;
2121const windows = std.os.windows;
22const ws2_32 = std.os.windows.ws2_32;
22const ws2_32 = windows.ws2_32;
2323
2424/// Thread-safe.
2525///
......@@ -2609,8 +2609,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
26092609 // opportunity to find additional ready operations.
26102610 break :t 0;
26112611 }
2612 const max_poll_ms = std.math.maxInt(i32);
2613 break :t max_poll_ms;
2612 break :t std.math.maxInt(i32);
26142613 };
26152614 const syscall = try Syscall.start();
26162615 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
27302729 break :allocation allocation;
27312730 };
27322731 @memcpy(slice[0..poll_buffer_len], storage.slice);
2732 storage.slice = slice;
27332733 }
27342734 storage.slice[len] = .{
27352735 .fd = file.handle,
......@@ -2783,9 +2783,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
27832783 }
27842784 const d = deadline orelse break :t -1;
27852785 const duration = d.durationFromNow(t_io);
2786 if (duration.raw.nanoseconds <= 0) return error.Timeout;
2787 const max_poll_ms = std.math.maxInt(i32);
2788 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
2786 break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32));
27892787 };
27902788 const syscall = try Syscall.start();
27912789 const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);
......@@ -14420,7 +14418,10 @@ const WindowsEnvironStrings = struct {
1442014418 PATHEXT: ?[:0]const u16 = null,
1442114419
1442214420 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
1442514426 var result: WindowsEnvironStrings = .{};
1442614427 var i: usize = 0;
......@@ -14446,7 +14447,7 @@ const WindowsEnvironStrings = struct {
1444614447
1444714448 inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
1444814449 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;
1445014451 }
1445114452 }
1445214453
......@@ -14465,29 +14466,46 @@ fn scanEnviron(t: *Threaded) void {
1446514466 // This value expires with any call that modifies the environment,
1446614467 // which is outside of this Io implementation's control, so references
1446714468 // 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
1447014474 var i: usize = 0;
1447114475 while (ptr[i] != 0) {
14472 const key_start = i;
1447314476
1447414477 // There are some special environment variables that start with =,
1447514478 // so we need a special case to not treat = as a key/value separator
1447614479 // if it's the first character.
1447714480 // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
14478 if (ptr[key_start] == '=') i += 1;
14479
14481 const key_start = i;
14482 if (ptr[i] == '=') i += 1;
1448014483 while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
1448114484 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' })) {
1448314492 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' })) {
1448514494 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 };
1448614507 }
1448714508 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
1449114509 }
1449214510 } else if (native_os == .wasi and !builtin.link_libc) {
1449314511 var environ_count: usize = undefined;
......@@ -14549,20 +14567,9 @@ fn scanEnviron(t: *Threaded) void {
1454914567 t.environ.exist.CLICOLOR_FORCE = true;
1455014568 } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
1455114569 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;
1456014570 break :file .{
14561 .handle = switch (@typeInfo(File.Handle)) {
14562 .int => int,
14563 .pointer => @ptrFromInt(int),
14564 else => comptime unreachable,
14565 },
14571 .handle = std.fmt.parseInt(u31, value, 10) catch
14572 break :file error.UnrecognizedFormat,
1456614573 .flags = .{ .nonblocking = true },
1456714574 };
1456814575 };
......@@ -14668,16 +14675,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1466814675 const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
1466914676 const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
1467014677
14671 const prog_pipe: [2]posix.fd_t = p: {
14672 if (options.progress_node.index == .none) {
14673 break :p .{ -1, -1 };
14674 } else {
14675 // We use CLOEXEC for the same reason as in `pipe_flags`.
14676 break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
14677 }
14678 };
14678 const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none)
14679 // We use CLOEXEC for the same reason as in `pipe_flags`.
14680 try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true })
14681 else
14682 .{ -1, -1 };
1467914683 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
1468114689 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
1468214690 defer arena_allocator.deinit();
1468314691 const arena = arena_allocator.allocator();
......@@ -14801,7 +14809,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
1480114809 if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
1480214810
1480314811 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
1480614814 return .{
1480714815 .pid = pid,
......@@ -15259,8 +15267,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1525915267
1526015268 const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
1526115269 .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
15262 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
15270 .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } },
1526315271 .inbound = true,
15272 .quota = std.Progress.max_packet_len * 2,
1526415273 }) else undefined;
1526515274 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
1547615485
1547715486 if (options.progress_node.index != .none) {
1547815487 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 } });
1548015489 }
1548115490
1548215491 return .{
lib/std/Progress.zig+466-468
......@@ -11,7 +11,7 @@ const windows = std.os.windows;
1111const testing = std.testing;
1212const assert = std.debug.assert;
1313const posix = std.posix;
14const Writer = std.Io.Writer;
14const Writer = Io.Writer;
1515
1616/// Currently this API only supports this value being set to stderr, which
1717/// happens automatically inside `start`.
......@@ -21,13 +21,10 @@ io: Io,
2121
2222terminal_mode: TerminalMode,
2323
24update_worker: ?Io.Future(void),
24update_worker: ?Io.Future(WorkerError!void),
2525
2626/// Atomically set by SIGWINCH as well as the root done() function.
2727redraw_event: Io.Event,
28/// Indicates a request to shut down and reset global state.
29/// Accessed atomically.
30done: bool,
3128need_clear: bool,
3229status: Status,
3330
......@@ -43,15 +40,19 @@ draw_buffer: []u8,
4340/// This is in a separate array from `node_storage` but with the same length so
4441/// that it can be iterated over efficiently without trashing too much of the
4542/// CPU cache.
46node_parents: []Node.Parent,
47node_storage: []Node.Storage,
48node_freelist_next: []Node.OptionalIndex,
43node_parents: [node_storage_buffer_len]Node.Parent,
44node_storage: [node_storage_buffer_len]Node.Storage,
45node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex,
4946node_freelist: Freelist,
5047/// This is the number of elements in node arrays which have been used so far. Nodes before this
5148/// index are either active, or on the freelist. The remaining nodes are implicitly free. This
5249/// value may at times temporarily exceed the node count.
5350node_end_index: u32,
5451
52ipc_next: Ipc.SlotAtomic,
53ipc: [ipc_storage_buffer_len]Ipc,
54ipc_files: [ipc_storage_buffer_len]Io.File,
55
5556start_failure: StartFailure,
5657
5758pub const Status = enum {
......@@ -77,6 +78,80 @@ const Freelist = packed struct(u32) {
7778 generation: u24,
7879};
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
80155pub const TerminalMode = union(enum) {
81156 off,
82157 ansi_escape_codes,
......@@ -116,7 +191,7 @@ pub const Node = struct {
116191
117192 pub const none: Node = .{ .index = .none };
118193
119 pub const max_name_len = 40;
194 pub const max_name_len = 120;
120195
121196 const Storage = extern struct {
122197 /// Little endian.
......@@ -127,25 +202,16 @@ pub const Node = struct {
127202 name: [max_name_len]u8 align(@alignOf(usize)),
128203
129204 /// Not thread-safe.
130 fn getIpcFd(s: Storage) ?Io.File.Handle {
131 return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) {
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;
205 fn getIpcIndex(s: Storage) ?Ipc.Index {
206 return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null;
136207 }
137208
138209 /// Thread-safe.
139 fn setIpcFd(s: *Storage, fd: Io.File.Handle) 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 };
210 fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) void {
145211 // `estimated_total_count` max int indicates the special state that
146212 // causes `completed_count` to be treated as a file descriptor, so
147213 // the order here matters.
148 @atomicStore(u32, &s.completed_count, integer, .monotonic);
214 @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic);
149215 @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize`
150216 }
151217
......@@ -155,6 +221,14 @@ pub const Node = struct {
155221 s.estimated_total_count = @byteSwap(s.estimated_total_count);
156222 }
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
158232 comptime {
159233 assert((@sizeOf(Storage) % 4) == 0);
160234 }
......@@ -242,7 +316,7 @@ pub const Node = struct {
242316 }
243317
244318 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) {
246320 // Ran out of node storage memory. Progress for this node will not be tracked.
247321 _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic);
248322 return Node.none;
......@@ -292,15 +366,17 @@ pub const Node = struct {
292366 const index = n.index.unwrap() orelse return;
293367 const storage = storageByIndex(index);
294368 // Avoid u32 max int which is used to indicate a special state.
295 const saturated = @min(std.math.maxInt(u32) - 1, count);
296 @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic);
369 const saturated_total_count = @min(std.math.maxInt(u32) - 1, count);
370 @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic);
297371 }
298372
299373 /// Thread-safe.
300374 pub fn increaseEstimatedTotalItems(n: Node, count: usize) void {
301375 const index = n.index.unwrap() orelse return;
302376 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);
304380 }
305381
306382 /// Finish a started `Node`. Thread-safe.
......@@ -310,11 +386,25 @@ pub const Node = struct {
310386 return;
311387 }
312388 const index = n.index.unwrap() orelse return;
389 const io = global_progress.io;
313390 const parent_ptr = parentByIndex(index);
314391 if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| {
315392 _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic);
316393 @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
318408 const freelist = &global_progress.node_freelist;
319409 var old_freelist = @atomicLoad(Freelist, freelist, .monotonic);
320410 while (true) {
......@@ -332,42 +422,52 @@ pub const Node = struct {
332422 };
333423 }
334424 } else {
335 @atomicStore(bool, &global_progress.done, true, .monotonic);
336 const io = global_progress.io;
337 global_progress.redraw_event.set(io);
338 if (global_progress.update_worker) |*worker| worker.await(io);
425 if (global_progress.update_worker) |*worker| worker.cancel(io) catch {};
426 for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| {
427 assert(!ipc.locked or !ipc.valid); // missing call to end()
428 if (ipc.locked or ipc.valid) ipc_file.close(io);
429 }
339430 }
340431 }
341432
342 /// Posix-only. Used by `std.process.Child`. Thread-safe.
343 pub fn setIpcFd(node: Node, fd: Io.File.Handle) void {
433 /// Used by `std.process.Child`. Thread-safe.
434 pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void {
344435 const index = node.index.unwrap() orelse return;
345 switch (@typeInfo(Io.File.Handle)) {
346 .int => {
347 assert(fd >= 0);
348 assert(fd != posix.STDOUT_FILENO);
349 assert(fd != posix.STDIN_FILENO);
350 assert(fd != posix.STDERR_FILENO);
351 },
352 .pointer => {
353 assert(fd != windows.INVALID_HANDLE_VALUE);
354 },
355 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
356 }
357 storageByIndex(index).setIpcFd(fd);
436 const io = global_progress.io;
437 assert(io.userdata == expected_io_userdata);
438 for (0..ipc_storage_buffer_len) |_| {
439 const slot: Ipc.Slot = @truncate(
440 @atomicRmw(Ipc.SlotAtomic, &global_progress.ipc_next, .Add, 1, .monotonic),
441 );
442 if (slot >= ipc_storage_buffer_len) continue;
443 const ipc_ptr = &global_progress.ipc[slot];
444 const ipc = @atomicLoad(Ipc, ipc_ptr, .monotonic);
445 if (ipc.locked or ipc.valid) continue;
446 const generation = ipc.generation +% 1;
447 if (@cmpxchgWeak(
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);
358459 }
359460
360 /// Posix-only. Thread-safe. Assumes the node is storing an IPC file
361 /// descriptor.
362 pub fn getIpcFd(node: Node) ?Io.File.Handle {
363 const index = node.index.unwrap() orelse return null;
364 const storage = storageByIndex(index);
365 const int = @atomicLoad(u32, &storage.completed_count, .monotonic);
366 return switch (@typeInfo(Io.File.Handle)) {
367 .int => @bitCast(int),
368 .pointer => @ptrFromInt(int),
369 else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)),
370 };
461 pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void {
462 storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index);
463 }
464
465 /// Not thread-safe.
466 pub fn takeIpcIndex(node: Node) ?Ipc.Index {
467 const storage = storageByIndex(node.index.unwrap() orelse return null);
468 assert(storage.estimated_total_count == std.math.maxInt(u32));
469 @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic);
470 return @bitCast(storage.completed_count);
371471 }
372472
373473 fn storageByIndex(index: Node.Index) *Node.Storage {
......@@ -387,7 +487,9 @@ pub const Node = struct {
387487
388488 const storage = storageByIndex(free_index);
389489 @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);
391493 const name_len = @min(max_name_len, name.len);
392494 copyAtomicStore(storage.name[0..name_len], name[0..name_len]);
393495 if (name_len < storage.name.len)
......@@ -414,16 +516,20 @@ var global_progress: Progress = .{
414516 .rows = 0,
415517 .cols = 0,
416518 .draw_buffer = undefined,
417 .done = false,
418519 .need_clear = false,
419520 .status = .working,
420 .start_failure = .unstarted,
421521
422 .node_parents = &node_parents_buffer,
423 .node_storage = &node_storage_buffer,
424 .node_freelist_next = &node_freelist_next_buffer,
522 .node_parents = undefined,
523 .node_storage = undefined,
524 .node_freelist_next = undefined,
425525 .node_freelist = .{ .head = .none, .generation = 0 },
426526 .node_end_index = 0,
527
528 .ipc_next = 0,
529 .ipc = undefined,
530 .ipc_files = undefined,
531
532 .start_failure = .unstarted,
427533};
428534
429535pub const StartFailure = union(enum) {
......@@ -433,17 +539,23 @@ pub const StartFailure = union(enum) {
433539 parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat },
434540};
435541
436const node_storage_buffer_len = 83;
437var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
438var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;
439var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined;
542/// One less than a power of two ensures `max_packet_len` is already a power of two.
543const node_storage_buffer_len = ipc_storage_buffer_len - 1;
544
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
441553var default_draw_buffer: [4096]u8 = undefined;
442554
443555var debug_start_trace = std.debug.Trace.init;
444556
445557pub const have_ipc = switch (builtin.os.tag) {
446 .wasi, .freestanding, .windows => false,
558 .wasi, .freestanding => false,
447559 else => true,
448560};
449561
......@@ -475,9 +587,9 @@ pub fn start(io: Io, options: Options) Node {
475587 }
476588 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 });
479592 const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items);
480 global_progress.done = false;
481593 global_progress.node_end_index = 1;
482594
483595 assert(options.draw_buffer.len >= 200);
......@@ -551,58 +663,55 @@ pub fn setStatus(new_status: Status) void {
551663}
552664
553665/// 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 {
555667 const timeout: Io.Timeout = .{ .duration = .{
556668 .clock = .awake,
557669 .raw = .fromNanoseconds(timeout_ns),
558670 } };
559671 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,
561674 };
562675 global_progress.redraw_event.reset();
563676 return resize_flag or (global_progress.cols == 0);
564677}
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 {
567683 // Store this data in the thread so that it does not need to be part of the
568684 // linker data of the main executable.
569685 var serialized_buffer: Serialized.Buffer = undefined;
686 serialized_buffer.init();
687 defer serialized_buffer.batch.cancel(io);
570688
571689 // In this function we bypass the wrapper code inside `Io.lockStderr` /
572690 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.
573691 // We still want to go through the `Io` instance however in case it uses a
574692 // task-switching mutex.
575693
576 {
577 const resize_flag = wait(io, global_progress.initial_delay_ns);
578 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
579 maybeUpdateSize(io, resize_flag) catch return;
580
581 const buffer, _ = computeRedraw(&serialized_buffer);
582 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
583 defer io.unlockStderr();
584 global_progress.need_clear = true;
585 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
586 }
694 try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
695 errdefer {
696 const cancel_protection = io.swapCancelProtection(.blocked);
697 defer _ = io.swapCancelProtection(cancel_protection);
698 const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
699 error.Canceled => unreachable, // blocked
700 };
701 defer io.unlockStderr();
702 clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
587703 }
588
589704 while (true) {
590 const resize_flag = wait(io, global_progress.refresh_rate_ns);
591
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| {
705 const buffer, _ = try computeRedraw(io, &serialized_buffer);
706 if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| {
602707 defer io.unlockStderr();
603708 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 };
605712 }
713
714 try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
606715 }
607716}
608717
......@@ -614,79 +723,60 @@ fn windowsApiWriteMarker() void {
614723 _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null);
615724}
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.
618729 var serialized_buffer: Serialized.Buffer = undefined;
730 serialized_buffer.init();
731 defer serialized_buffer.batch.cancel(io);
619732
620733 // In this function we bypass the wrapper code inside `Io.lockStderr` /
621734 // `Io.tryLockStderr` in order to avoid clearing the terminal twice.
622735 // We still want to go through the `Io` instance however in case it uses a
623736 // task-switching mutex.
624737
625 {
626 const resize_flag = wait(io, global_progress.initial_delay_ns);
627 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
628 maybeUpdateSize(io, resize_flag) catch return;
629
630 const buffer, const nl_n = computeRedraw(&serialized_buffer);
631 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
632 defer io.unlockStderr();
633 windowsApiWriteMarker();
634 global_progress.need_clear = true;
635 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
636 windowsApiMoveToMarker(nl_n) catch return;
637 }
738 try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns));
739 errdefer {
740 const cancel_protection = io.swapCancelProtection(.blocked);
741 defer _ = io.swapCancelProtection(cancel_protection);
742 _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) {
743 error.Canceled => unreachable, // blocked
744 };
745 defer io.unlockStderr();
746 clearWrittenWindowsApi() catch {};
638747 }
639
640748 while (true) {
641 const resize_flag = wait(io, global_progress.refresh_rate_ns);
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);
749 const buffer, const nl_n = try computeRedraw(io, &serialized_buffer);
652750 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
653751 defer io.unlockStderr();
654 clearWrittenWindowsApi() catch return;
752 try clearWrittenWindowsApi();
655753 windowsApiWriteMarker();
656754 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 };
658758 windowsApiMoveToMarker(nl_n) catch return;
659759 }
760
761 try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns));
660762 }
661763}
662764
663fn ipcThreadRun(io: Io, file: Io.File) void {
765fn ipcThreadRun(io: Io, file: Io.File) WorkerError!void {
664766 // Store this data in the thread so that it does not need to be part of the
665767 // linker data of the main executable.
666768 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 {
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
773 _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake);
680774 while (true) {
681 _ = wait(io, global_progress.refresh_rate_ns);
682
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,
775 writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) {
776 error.WriteFailed => return fw.err.?,
689777 };
778
779 _ = try io.sleep(.fromNanoseconds(global_progress.refresh_rate_ns), .awake);
690780 }
691781}
692782
......@@ -865,31 +955,49 @@ const Serialized = struct {
865955 const Buffer = struct {
866956 parents: [node_storage_buffer_len]Node.Parent,
867957 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,
871 storage_copy: [node_storage_buffer_len]Node.Storage,
872 ipc_metadata_fds_copy: [node_storage_buffer_len]Fd,
873 ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata,
874
875 ipc_metadata_fds: [node_storage_buffer_len]Fd,
876 ipc_metadata: [node_storage_buffer_len]SavedMetadata,
959 ipc_start: u8,
960 ipc_end: u8,
961 ipc_data: [ipc_storage_buffer_len]Ipc.Data,
962 ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8,
963 ipc_vecs: [ipc_storage_buffer_len][1][]u8,
964 batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage,
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 }
877973 };
878974};
879975
880fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
881 var serialized_len: usize = 0;
882 var any_ipc = false;
976fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized {
977 var prev_parents: [node_storage_buffer_len]Node.Parent = undefined;
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
884986 // Iterate all of the nodes and construct a serializable copy of the state that can be examined
885987 // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the
886988 // 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;
888996 for (
889997 global_progress.node_parents[0..end_index],
890998 global_progress.node_storage[0..end_index],
891 serialized_buffer.map[0..end_index],
892 ) |*parent_ptr, *storage_ptr, *map| {
999 map[0..end_index],
1000 ) |*parent_ptr, *storage_ptr, *map_entry| {
8931001 const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic);
8941002 if (parent == .unused) {
8951003 // 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 {
9031011 // parent, it will just not be printed at all. The general idea here is that performance
9041012 // is more important than 100% correct output every frame, given that this API is likely
9051013 // to be used in hot paths!
906 map.* = .none;
1014 map_entry.* = .none;
9071015 continue;
9081016 }
9091017 const dest_storage = &serialized_buffer.storage[serialized_len];
9101018 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`
9121020 dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);
9131021
914 any_ipc = any_ipc or (dest_storage.getIpcFd() != null);
9151022 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;
9171025 serialized_len += 1;
9181026 }
9191027
......@@ -922,266 +1030,212 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
9221030 parent.* = switch (parent.*) {
9231031 .unused => unreachable,
9241032 .none => .none,
925 _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(),
1033 _ => |p| map[@intFromEnum(p)].toParent(),
9261034 };
9271035 }
9281036
929 // Find nodes which correspond to child processes.
930 if (any_ipc)
931 serialized_len = serializeIpc(serialized_len, serialized_buffer);
932
933 return .{
934 .parents = serialized_buffer.parents[0..serialized_len],
935 .storage = serialized_buffer.storage[0..serialized_len],
1037 // Fill pipe buffers.
1038 const batch = &serialized_buffer.batch;
1039 batch.awaitConcurrent(io, .{
1040 .duration = .{ .raw = .zero, .clock = .awake },
1041 }) catch |err| switch (err) {
1042 error.Timeout => {},
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,
9361077 };
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 (
978 serialized_buffer.parents[0..serialized_len],
979 serialized_buffer.storage[0..serialized_len],
980 0..,
1079 // Find nodes which correspond to child processes.
1080 const ipc_start = maybe_ipc_start orelse serialized_len;
1081 serialized_buffer.ipc_start = ipc_start;
1082 for (
1083 serialized_buffer.parents[ipc_start..serialized_len],
1084 serialized_buffer.storage[ipc_start..serialized_len],
1085 ipc_start..,
9811086 ) |main_parent, *main_storage, main_index| {
9821087 if (main_parent == .unused) continue;
983 const file: Io.File = .{
984 .handle = main_storage.getIpcFd() orelse continue,
985 .flags = .{ .nonblocking = true },
986 };
987 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
988 var bytes_read: usize = 0;
989 while (true) {
990 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {
991 error.WouldBlock, error.EndOfStream => break,
992 else => |e| {
993 std.log.debug("failed to read child progress data: {t}", .{e});
994 main_storage.completed_count = 0;
995 main_storage.estimated_total_count = 0;
996 continue :main_loop;
997 },
998 };
999 if (opt_saved_metadata) |m| {
1000 if (m.remaining_read_trash_bytes > 0) {
1001 assert(bytes_read == 0);
1002 if (m.remaining_read_trash_bytes >= n) {
1003 m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n);
1004 continue;
1005 }
1006 const src = pipe_buf[m.remaining_read_trash_bytes..n];
1007 @memmove(pipe_buf[0..src.len], src);
1008 m.remaining_read_trash_bytes = 0;
1009 bytes_read = src.len;
1010 continue;
1011 }
1012 }
1013 bytes_read += n;
1014 }
1015 // Ignore all but the last message on the pipe.
1016 var input: []u8 = pipe_buf[0..bytes_read];
1017 if (input.len == 0) {
1018 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle);
1019 continue;
1020 }
1021
1022 const storage, const parents = while (true) {
1023 const subtree_len: usize = input[0];
1024 const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent));
1025 if (input.len < expected_bytes) {
1026 // Ignore short reads. We'll handle the next full message when it comes instead.
1027 const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len);
1028 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle);
1029 continue :main_loop;
1030 }
1031 if (input.len > expected_bytes) {
1032 input = input[expected_bytes..];
1033 continue;
1034 }
1035 const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)];
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));
1088 const ipc_index = main_storage.getIpcIndex() orelse continue;
1089 const ipc = &global_progress.ipc[ipc_index.slot];
1090 const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot];
1091 state: switch (ipc_data.state) {
1092 .unused => {
1093 if (@cmpxchgWeak(
1094 Ipc,
1095 ipc,
1096 .{ .locked = false, .valid = true, .generation = ipc_index.generation },
1097 .{ .locked = true, .valid = true, .generation = ipc_index.generation },
1098 .acquire,
1099 .monotonic,
1100 )) |_| continue;
1101
1102 const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot];
1103 ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]};
1104 batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{
1105 .file = global_progress.ipc_files[ipc_index.slot],
1106 .data = ipc_vec,
1107 } });
1108
1109 ipc_data.* = .{
1110 .state = .pending,
1111 .bytes_read = 0,
1112 .main_index = @intCast(main_index),
1113 .start_index = serialized_len,
1114 .nodes_len = 0,
1115 };
1116 main_storage.completed_count = 0;
1117 main_storage.estimated_total_count = 0;
1118 },
1119 .pending => {
1120 const start_index = ipc_data.start_index;
1121 const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len);
1122
1123 main_storage.copyRoot(&prev_storage[ipc_data.main_index]);
1124 @memcpy(
1125 serialized_buffer.storage[serialized_len..][0..nodes_len],
1126 prev_storage[start_index..][0..nodes_len],
1127 );
1128 for (
1129 serialized_buffer.parents[serialized_len..][0..nodes_len],
1130 prev_parents[serialized_len..][0..nodes_len],
1131 ) |*parent, prev_parent| parent.* = switch (prev_parent) {
1132 .none, .unused => .none,
1133 _ => if (@intFromEnum(prev_parent) == ipc_data.main_index)
1134 @enumFromInt(main_index)
1135 else if (@intFromEnum(prev_parent) >= start_index and
1136 @intFromEnum(prev_parent) < start_index + nodes_len)
1137 @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len)
1138 else
1139 .none,
1140 };
10441141
1045 // Remember in case the pipe is empty on next update.
1046 ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle);
1047 ipc_metadata[ipc_metadata_len] = .{
1048 .remaining_read_trash_bytes = 0,
1049 .start_index = @intCast(serialized_len),
1050 .nodes_len = nodes_len,
1051 .main_index = @intCast(main_index),
1052 };
1053 ipc_metadata_len += 1;
1054
1055 // Mount the root here.
1056 copyRoot(main_storage, &storage[0]);
1057 if (is_big_endian) main_storage.byteSwap();
1058
1059 // Copy the rest of the tree to the end.
1060 const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len];
1061 @memcpy(storage_dest, storage[1..][0..nodes_len]);
1062
1063 // Always little-endian over the pipe.
1064 if (is_big_endian) for (storage_dest) |*s| s.byteSwap();
1065
1066 // Patch up parent pointers taking into account how the subtree is mounted.
1067 for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| {
1068 dest.* = switch (p) {
1069 // Fix bad data so the rest of the code does not see `unused`.
1070 .none, .unused => .none,
1071 // Root node is being mounted here.
1072 @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index),
1073 // Other nodes mounted at the end.
1074 // Don't trust child data; if the data is outside the expected range, ignore the data.
1075 // This also handles the case when data was truncated.
1076 _ => |off| if (@intFromEnum(off) > nodes_len)
1077 .none
1078 else
1079 @enumFromInt(serialized_len + @intFromEnum(off) - 1),
1080 };
1142 ipc_data.main_index = @intCast(main_index);
1143 ipc_data.start_index = serialized_len;
1144 ipc_data.nodes_len = nodes_len;
1145 serialized_len += nodes_len;
1146 },
1147 .ready => {
1148 const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot];
1149 const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer);
1150 const packet_is_empty = packet_end - packet_start <= 1;
1151 if (!packet_is_empty) {
1152 const storage, const parents, const nodes_len = packet_contents: {
1153 var packet_index: usize = packet_start;
1154 const nodes_len: u16 = ipc_buffer[packet_index];
1155 packet_index += 1;
1156 const storage_bytes =
1157 ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)];
1158 packet_index += storage_bytes.len;
1159 const parents_bytes =
1160 ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)];
1161 packet_index += parents_bytes.len;
1162 assert(packet_index == packet_end);
1163 const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes);
1164 const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes);
1165 const children_nodes_len =
1166 @min(nodes_len - 1, node_storage_buffer_len - serialized_len);
1167 break :packet_contents .{ storage, parents, children_nodes_len };
1168 };
1169
1170 // Mount the root here.
1171 main_storage.copyRoot(&storage[0]);
1172 if (is_big_endian) main_storage.byteSwap();
1173
1174 // Copy the rest of the tree to the end.
1175 const serialized_storage =
1176 serialized_buffer.storage[serialized_len..][0..nodes_len];
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 },
10811208 }
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;
11111209 }
1112 return null;
1113}
1114
1115fn useSavedIpcData(
1116 start_serialized_len: usize,
1117 serialized_buffer: *Serialized.Buffer,
1118 main_storage: *Node.Storage,
1119 main_index: usize,
1120 opt_saved_metadata: ?*SavedMetadata,
1121 remaining_read_trash_bytes: u16,
1122 fd: Io.File.Handle,
1123) usize {
1124 const parents_copy = &serialized_buffer.parents_copy;
1125 const storage_copy = &serialized_buffer.storage_copy;
1126 const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds;
1127 const ipc_metadata = &serialized_buffer.ipc_metadata;
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;
1210 serialized_buffer.ipc_end = serialized_len;
1211
1212 // Ignore data from unused pipes. This ensures that if a child process exists we will
1213 // eventually see `EndOfStream` and close the pipe.
1214 if (ready_len > 0) for (
1215 &serialized_buffer.ipc_data,
1216 &serialized_buffer.ipc_buffers,
1217 &serialized_buffer.ipc_vecs,
1218 0..,
1219 ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) {
1220 .unused, .pending => {},
1221 .ready => {
1222 _, const packet_end = ipc_data.findLastPacket(ipc_buffer);
1223 ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end);
1224 ready_len -= 1;
1225 },
11431226 };
1227 assert(ready_len == 0);
11441228
1145 const start_index = saved_metadata.start_index;
1146 const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len);
1147 const old_main_index = saved_metadata.main_index;
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),
1229 return .{
1230 .parents = serialized_buffer.parents[0..serialized_len],
1231 .storage = serialized_buffer.storage[0..serialized_len],
11551232 };
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;
11811233}
11821234
1183fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } {
1184 const serialized = serialize(serialized_buffer);
1235fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } {
1236 if (global_progress.rows == 0 or global_progress.cols == 0) return error.WindowTooSmall;
1237
1238 const serialized = try serialize(io, serialized_buffer);
11851239
11861240 // Now we can analyze our copy of the graph without atomics, reconstructing
11871241 // children lists which do not exist in the canonical data. These are
......@@ -1416,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
14161470 return nl_n + 2 < p.rows;
14171471}
14181472
1419var remaining_write_trash_bytes: usize = 0;
1420
1421fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {
1473fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void {
14221474 // Byteswap if necessary to ensure little endian over the pipe. This is
14231475 // needed because the parent or child process might be running in qemu.
14241476 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
14291481 const storage = std.mem.sliceAsBytes(serialized.storage);
14301482 const parents = std.mem.sliceAsBytes(serialized.parents);
14311483
1432 var vecs: [3][]const u8 = .{ header, storage, parents };
1433
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 }
1484 var vec = [3][]const u8{ header, storage, parents };
1485 try writer.writeVecAll(&vec);
14881486}
14891487
14901488fn maybeUpdateSize(io: Io, resize_flag: bool) !void {
lib/std/os/linux.zig+18
......@@ -1848,6 +1848,24 @@ pub const F = struct {
18481848 pub const RDLCK = if (is_sparc) 1 else 0;
18491849 pub const WRLCK = if (is_sparc) 2 else 1;
18501850 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;
18511869};
18521870
18531871pub const F_OWNER = enum(i32) {