std.Progress: implement ipc resource cleanup

This commit is contained in:
Jacob Young 2026-01-31 20:22:53 -05:00
parent ffc6da29e3
commit 71156aff80
4 changed files with 556 additions and 543 deletions

View file

@ -386,10 +386,14 @@ pub const ZigProcess = struct {
child: std.process.Child,
multi_reader_buffer: Io.File.MultiReader.Buffer(2),
multi_reader: Io.File.MultiReader,
progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn,
pub const StreamEnum = enum { stdout, stderr };
pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void {
zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null;
}
pub fn deinit(zp: *ZigProcess, io: Io) void {
zp.child.kill(io);
zp.multi_reader.deinit();
@ -417,7 +421,14 @@ pub fn evalZigProcess(
if (s.getZigProcess()) |zp| update: {
assert(watch);
if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);
if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index);
zp.progress_ipc_index = null;
var exited = false;
defer if (exited) {
s.cast(Compile).?.zig_process = null;
zp.deinit(io);
gpa.destroy(zp);
} else zp.saveState(prog_node);
const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
error.BrokenPipe, error.EndOfStream => |reason| {
std.log.info("{s} restart required: {t}", .{ argv[0], reason });
@ -426,7 +437,7 @@ pub fn evalZigProcess(
return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
};
_ = term;
s.clearZigProcess(gpa);
exited = true;
break :update;
},
else => |e| return e,
@ -442,7 +453,7 @@ pub fn evalZigProcess(
return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
};
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
s.clearZigProcess(gpa);
exited = true;
try handleChildProcessTerm(s, term);
return error.MakeFailed;
}
@ -467,19 +478,16 @@ pub fn evalZigProcess(
.progress_node = prog_node,
}) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
zp.* = .{
.child = zp.child,
.multi_reader_buffer = undefined,
.multi_reader = undefined,
.progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},
};
zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
zp.child.stdout.?, zp.child.stderr.?,
});
if (watch) s.setZigProcess(zp);
if (watch) s.cast(Compile).?.zig_process = zp;
defer if (!watch) zp.deinit(io);
const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);
const result = result: {
defer if (watch) zp.saveState(prog_node);
break :result try zigProcessUpdate(s, zp, watch, web_server, gpa);
};
if (!watch) {
// Send EOF to stdin.
@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess {
};
}
fn setZigProcess(s: *Step, zp: *ZigProcess) void {
switch (s.id) {
.compile => s.cast(Compile).?.zig_process = zp,
else => unreachable,
}
}
fn clearZigProcess(s: *Step, gpa: Allocator) void {
switch (s.id) {
.compile => {
const compile = s.cast(Compile).?;
if (compile.zig_process) |zp| {
gpa.destroy(zp);
compile.zig_process = null;
}
},
else => unreachable,
}
}
fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void {
const header: std.zig.Client.Message.Header = .{
.tag = tag,

View file

@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment;
const assert = std.debug.assert;
const posix = std.posix;
const windows = std.os.windows;
const ws2_32 = std.os.windows.ws2_32;
const ws2_32 = windows.ws2_32;
/// Thread-safe.
///
@ -2609,8 +2609,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
// opportunity to find additional ready operations.
break :t 0;
}
const max_poll_ms = std.math.maxInt(i32);
break :t max_poll_ms;
break :t std.math.maxInt(i32);
};
const syscall = try Syscall.start();
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
break :allocation allocation;
};
@memcpy(slice[0..poll_buffer_len], storage.slice);
storage.slice = slice;
}
storage.slice[len] = .{
.fd = file.handle,
@ -2783,9 +2783,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
}
const d = deadline orelse break :t -1;
const duration = d.durationFromNow(t_io);
if (duration.raw.nanoseconds <= 0) return error.Timeout;
const max_poll_ms = std.math.maxInt(i32);
break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32));
};
const syscall = try Syscall.start();
const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);
@ -14420,7 +14418,10 @@ const WindowsEnvironStrings = struct {
PATHEXT: ?[:0]const u16 = null,
fn scan() WindowsEnvironStrings {
const ptr = windows.peb().ProcessParameters.Environment;
const peb = windows.peb();
assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
const ptr = peb.ProcessParameters.Environment;
var result: WindowsEnvironStrings = .{};
var i: usize = 0;
@ -14446,7 +14447,7 @@ const WindowsEnvironStrings = struct {
inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| {
const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name);
if (std.os.windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;
if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w;
}
}
@ -14465,29 +14466,46 @@ fn scanEnviron(t: *Threaded) void {
// This value expires with any call that modifies the environment,
// which is outside of this Io implementation's control, so references
// must be short-lived.
const ptr = windows.peb().ProcessParameters.Environment;
const peb = windows.peb();
assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS);
defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS);
const ptr = peb.ProcessParameters.Environment;
var i: usize = 0;
while (ptr[i] != 0) {
const key_start = i;
// There are some special environment variables that start with =,
// so we need a special case to not treat = as a key/value separator
// if it's the first character.
// https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
if (ptr[key_start] == '=') i += 1;
const key_start = i;
if (ptr[i] == '=') i += 1;
while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {}
const key_w = ptr[key_start..i];
if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
const value_start = i + 1;
while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
const value_w = ptr[value_start..i];
i += 1; // skip over null byte
if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) {
t.environ.exist.NO_COLOR = true;
} else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
} else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) {
t.environ.exist.CLICOLOR_FORCE = true;
} else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) {
t.environ.zig_progress_file = file: {
var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
const len = std.unicode.calcWtf8Len(value_w);
if (len > value_buf.len) break :file error.UnrecognizedFormat;
assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len);
break :file .{
.handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch
break :file error.UnrecognizedFormat),
.flags = .{ .nonblocking = true },
};
};
}
comptime assert(@sizeOf(Environ.String) == 0);
while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value
i += 1; // skip over null byte
}
} else if (native_os == .wasi and !builtin.link_libc) {
var environ_count: usize = undefined;
@ -14549,20 +14567,9 @@ fn scanEnviron(t: *Threaded) void {
t.environ.exist.CLICOLOR_FORCE = true;
} else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) {
t.environ.zig_progress_file = file: {
const int = std.fmt.parseInt(switch (@typeInfo(File.Handle)) {
.int => |int_info| @Int(
.unsigned,
int_info.bits - @intFromBool(int_info.signedness == .signed),
),
.pointer => usize,
else => break :file error.UnsupportedOperation,
}, value, 10) catch break :file error.UnrecognizedFormat;
break :file .{
.handle = switch (@typeInfo(File.Handle)) {
.int => int,
.pointer => @ptrFromInt(int),
else => comptime unreachable,
},
.handle = std.fmt.parseInt(u31, value, 10) catch
break :file error.UnrecognizedFormat,
.flags = .{ .nonblocking = true },
};
};
@ -14668,16 +14675,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore);
const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined;
const prog_pipe: [2]posix.fd_t = p: {
if (options.progress_node.index == .none) {
break :p .{ -1, -1 };
} else {
// We use CLOEXEC for the same reason as in `pipe_flags`.
break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
}
};
const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none)
// We use CLOEXEC for the same reason as in `pipe_flags`.
try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true })
else
.{ -1, -1 };
errdefer destroyPipe(prog_pipe);
if (native_os == .linux and prog_pipe[0] != -1) {
_ = posix.system.fcntl(prog_pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2));
}
var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
@ -14801,7 +14809,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
if (options.stderr == .pipe) posix.close(stderr_pipe[1]);
if (prog_pipe[1] != -1) posix.close(prog_pipe[1]);
options.progress_node.setIpcFd(prog_pipe[0]);
options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
return .{
.pid = pid,
@ -15259,8 +15267,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{
.server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } },
.client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
.client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } },
.inbound = true,
.quota = std.Progress.max_packet_len * 2,
}) else undefined;
errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle);
@ -15476,7 +15485,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
if (options.progress_node.index != .none) {
windows.CloseHandle(prog_pipe[1]);
options.progress_node.setIpcFd(prog_pipe[0]);
options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
}
return .{

File diff suppressed because it is too large Load diff

View file

@ -1848,6 +1848,24 @@ pub const F = struct {
pub const RDLCK = if (is_sparc) 1 else 0;
pub const WRLCK = if (is_sparc) 2 else 1;
pub const UNLCK = if (is_sparc) 3 else 2;
pub const LINUX_SPECIFIC_BASE = 1024;
pub const SETLEASE = LINUX_SPECIFIC_BASE + 0;
pub const GETLEASE = LINUX_SPECIFIC_BASE + 1;
pub const NOTIFY = LINUX_SPECIFIC_BASE + 2;
pub const DUPFD_QUERY = LINUX_SPECIFIC_BASE + 3;
pub const CREATED_QUERY = LINUX_SPECIFIC_BASE + 4;
pub const CANCELLK = LINUX_SPECIFIC_BASE + 5;
pub const DUPFD_CLOEXEC = LINUX_SPECIFIC_BASE + 6;
pub const SETPIPE_SZ = LINUX_SPECIFIC_BASE + 7;
pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8;
pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9;
pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10;
pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11;
pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12;
pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13;
pub const SET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 14;
};
pub const F_OWNER = enum(i32) {