mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 04:24:33 +01:00
201 lines
6.4 KiB
Zig
201 lines
6.4 KiB
Zig
const Child = @This();
|
|
|
|
const builtin = @import("builtin");
|
|
const native_os = builtin.os.tag;
|
|
|
|
const std = @import("../std.zig");
|
|
const Io = std.Io;
|
|
const process = std.process;
|
|
const File = std.Io.File;
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
pub const Id = switch (native_os) {
|
|
.windows => std.os.windows.HANDLE,
|
|
.wasi => void,
|
|
else => std.posix.pid_t,
|
|
};
|
|
|
|
/// After `wait` or `kill` is called, this becomes `null`.
|
|
/// On Windows this is the hProcess.
|
|
/// On POSIX this is the pid.
|
|
id: ?Id,
|
|
thread_handle: if (native_os == .windows) std.os.windows.HANDLE else void,
|
|
/// The writing end of the child process's standard input pipe.
|
|
/// Usage requires `process.SpawnOptions.StdIo.pipe`.
|
|
stdin: ?File,
|
|
/// The reading end of the child process's standard output pipe.
|
|
/// Usage requires `process.SpawnOptions.StdIo.pipe`.
|
|
stdout: ?File,
|
|
/// The reading end of the child process's standard error pipe.
|
|
/// Usage requires `process.SpawnOptions.StdIo.pipe`.
|
|
stderr: ?File,
|
|
/// This is available after calling wait if
|
|
/// `request_resource_usage_statistics` was set to `true` before calling
|
|
/// `spawn`.
|
|
/// TODO move this data into `Term`
|
|
resource_usage_statistics: ResourceUsageStatistics = .{},
|
|
request_resource_usage_statistics: bool,
|
|
|
|
pub const ResourceUsageStatistics = struct {
|
|
rusage: @TypeOf(rusage_init) = rusage_init,
|
|
|
|
/// Returns the peak resident set size of the child process, in bytes,
|
|
/// if available.
|
|
pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize {
|
|
switch (native_os) {
|
|
.dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .linux, .serenity => {
|
|
if (rus.rusage) |ru| {
|
|
return @as(usize, @intCast(ru.maxrss)) * 1024;
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
.windows => {
|
|
if (rus.rusage) |ru| {
|
|
return ru.PeakWorkingSetSize;
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
|
|
if (rus.rusage) |ru| {
|
|
// Darwin oddly reports in bytes instead of kilobytes.
|
|
return @as(usize, @intCast(ru.maxrss));
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
else => return null,
|
|
}
|
|
}
|
|
|
|
const rusage_init = switch (native_os) {
|
|
.dragonfly,
|
|
.freebsd,
|
|
.netbsd,
|
|
.openbsd,
|
|
.illumos,
|
|
.linux,
|
|
.serenity,
|
|
.driverkit,
|
|
.ios,
|
|
.maccatalyst,
|
|
.macos,
|
|
.tvos,
|
|
.visionos,
|
|
.watchos,
|
|
=> @as(?std.posix.rusage, null),
|
|
.windows => @as(?std.os.windows.VM_COUNTERS, null),
|
|
else => {},
|
|
};
|
|
};
|
|
|
|
pub const Term = union(enum) {
|
|
exited: u8,
|
|
signal: std.posix.SIG,
|
|
stopped: u32,
|
|
unknown: u32,
|
|
};
|
|
|
|
/// Requests for the operating system to forcibly terminate the child process,
|
|
/// then blocks until it terminates, then cleans up all resources.
|
|
///
|
|
/// Idempotent and does nothing after `wait` returns.
|
|
///
|
|
/// Uncancelable. Ignores unexpected errors from the operating system.
|
|
pub fn kill(child: *Child, io: Io) void {
|
|
if (child.id == null) {
|
|
assert(child.stdin == null);
|
|
assert(child.stdout == null);
|
|
assert(child.stderr == null);
|
|
return;
|
|
}
|
|
io.vtable.childKill(io.userdata, child);
|
|
assert(child.id == null);
|
|
}
|
|
|
|
pub const WaitError = error{
|
|
AccessDenied,
|
|
} || Io.Cancelable || Io.UnexpectedError;
|
|
|
|
/// Blocks until child process terminates and then cleans up all resources.
|
|
pub fn wait(child: *Child, io: Io) WaitError!Term {
|
|
assert(child.id != null);
|
|
return io.vtable.childWait(io.userdata, child);
|
|
}
|
|
|
|
pub const CollectOutputError = error{
|
|
StreamTooLong,
|
|
} || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error;
|
|
|
|
pub const CollectOutputOptions = struct {
|
|
stdout: *std.ArrayList(u8),
|
|
stderr: *std.ArrayList(u8),
|
|
/// Used for `stdout` and `stderr`. If not provided, only the existing
|
|
/// capacity will be used.
|
|
allocator: ?Allocator = null,
|
|
stdout_limit: Io.Limit = .unlimited,
|
|
stderr_limit: Io.Limit = .unlimited,
|
|
timeout: Io.Timeout = .none,
|
|
};
|
|
|
|
/// Collect the output from the process's stdout and stderr. Will return once
|
|
/// all output has been collected. This does not mean that the process has
|
|
/// ended. `wait` should still be called to wait for and clean up the process.
|
|
///
|
|
/// The process must have been started with stdout and stderr set to
|
|
/// `process.SpawnOptions.StdIo.pipe`.
|
|
pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void {
|
|
const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr };
|
|
const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit };
|
|
|
|
if (options.allocator) |gpa| {
|
|
for (lists) |list| try list.ensureUnusedCapacity(gpa, 1);
|
|
} else {
|
|
for (lists) |list| {
|
|
if (list.unusedCapacitySlice().len == 0)
|
|
return error.StreamTooLong;
|
|
}
|
|
}
|
|
|
|
var vecs: [2][1][]u8 = undefined;
|
|
for (lists, &vecs) |list, *vec|
|
|
vec[0] = list.unusedCapacitySlice();
|
|
|
|
var operations: [2]Io.Operation = .{
|
|
.{ .file_read_streaming = .{
|
|
.file = child.stdout.?,
|
|
.data = &vecs[0],
|
|
} },
|
|
.{ .file_read_streaming = .{
|
|
.file = child.stderr.?,
|
|
.data = &vecs[1],
|
|
} },
|
|
};
|
|
|
|
var batch: Io.Batch = .init(&operations);
|
|
batch.submit(io);
|
|
defer batch.cancel(io);
|
|
|
|
var pending = operations.len;
|
|
var retry_index: ?usize = null;
|
|
while (pending > 0) {
|
|
const resubmissions: []const usize = if (retry_index) |i| &.{i} else &.{};
|
|
const index = try batch.wait(io, resubmissions, options.timeout);
|
|
const n = try operations[index].file_read_streaming.status.result;
|
|
if (n == 0) {
|
|
pending -= 1;
|
|
} else {
|
|
retry_index = index;
|
|
const list = lists[index];
|
|
const limit = limits[index];
|
|
list.items.len += n;
|
|
if (list.items.len >= @intFromEnum(limit)) return error.StreamTooLong;
|
|
if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1);
|
|
const cap = list.unusedCapacitySlice();
|
|
if (cap.len == 0) return error.StreamTooLong;
|
|
vecs[index][0] = cap;
|
|
}
|
|
}
|
|
}
|