zig/lib/std/job/Client.zig
2025-12-05 11:47:13 +00:00

196 lines
7.6 KiB
Zig

impl: Impl,
pub const InitError = error{
OutOfMemory,
/// There is no advertised job server.
NoServer,
/// The job server is advertising a communication method which is not known.
UnknownMethod,
/// The job server is advertising a communication method which is known but unsupported.
UnsupportedMethod,
/// A job server advertisement exists, but is malformed.
InvalidArgument,
/// The job server has shut down or is otherwise not available to connect to.
ServerFailed,
/// This process does not have permission to access the job server.
AccessDenied,
};
pub fn init(arena: Allocator, env: *const std.process.EnvMap) InitError!Client {
const env_val = env.get("ROBUST_JOBSERVER") orelse return error.NoServer;
const idx = std.mem.findScalar(u8, env_val, ':') orelse return error.InvalidArgument;
const method = std.meta.stringToEnum(job.Method, env_val[0..idx]) orelse return error.UnknownMethod;
switch (method) {
inline else => |m| {
const ImplTy = @FieldType(Impl, @tagName(m));
if (ImplTy == noreturn) return error.UnsupportedMethod;
return .{ .impl = @unionInit(
Impl,
@tagName(m),
try .init(arena, env_val[idx + 1 ..]),
) };
},
}
}
pub fn deinit(c: *Client) void {
switch (c.impl) {
inline else => |*x| x.deinit(),
}
c.* = undefined;
}
pub const AcquireError = error{
/// The job server has shut down or is otherwise not available to connect to.
ServerFailed,
/// This process does not have permission to access the job server.
AccessDenied,
/// Insufficient resources are available to acquire a token.
SystemResources,
Unexpected,
};
pub fn acquire(c: *const Client) AcquireError!Token {
return switch (c.impl) {
inline else => |*impl| impl.acquire(),
};
}
const Impl = union(job.Method) {
sysvsem: if (job.sysv_sem.supported) SysVSem else noreturn,
win32pipe: if (builtin.target.os.tag == .windows) Win32Pipe else noreturn,
};
pub const Token = union(job.Method) {
sysvsem: if (job.sysv_sem.supported) SysVSem else noreturn,
win32pipe: if (builtin.target.os.tag == .windows) windows.HANDLE else noreturn,
pub fn release(t: Token) void {
switch (t) {
.sysvsem => |sem| while (true) {
return job.sysv_sem.modify(sem.set_id, 1) catch |err| switch (err) {
error.AccessDenied, error.InvalidSemaphore => {
// The semaphore broke somehow, but that's not our problem!
// (...at least, not until we next call `acquire`.)
},
error.SystemResources => unreachable, // the undo structure was already allocated in `acquire`
error.Interrupted => continue, // releasing can't block; just retry
error.Unexpected => {}, // already warned, nothing more we can do
};
},
.win32pipe => |handle| _ = windows.ntdll.NtClose(handle),
}
}
};
const SysVSem = struct {
set_id: i32,
fn init(arena: Allocator, arg: []const u8) InitError!SysVSem {
_ = arena;
const set_id = std.fmt.parseInt(i32, arg, 10) catch return error.InvalidArgument;
return .{ .set_id = set_id };
}
fn deinit(sem: SysVSem) void {
_ = sem;
}
fn acquire(sem: SysVSem) AcquireError!Token {
while (true) {
break job.sysv_sem.modify(sem.set_id, -1) catch |err| switch (err) {
error.InvalidSemaphore => return error.ServerFailed,
error.Interrupted => continue, // TODO: support cancelation
error.AccessDenied, error.SystemResources, error.Unexpected => |e| return e,
};
}
return .{ .sysvsem = sem };
}
};
const Win32Pipe = struct {
pipe_device: windows.HANDLE,
pipe_path: [:0]const u16,
fn init(arena: Allocator, arg: []const u8) InitError!Win32Pipe {
if (arg.len == 0) return error.InvalidArgument;
if (std.mem.findAny(u8, arg, "\\/\x00") != null) return error.InvalidArgument;
const pipe_path = std.unicode.wtf8ToWtf16LeAllocZ(
arena,
try std.fmt.allocPrint(arena, "\\??\\pipe\\{s}", .{arg}),
) catch |err| switch (err) {
error.InvalidWtf8 => return error.InvalidArgument,
error.OutOfMemory => |e| return e,
};
const pipe_device = windows.OpenFile(
std.unicode.wtf8ToWtf16LeStringLiteral("\\??\\pipe\\"),
.{
.access_mask = windows.SYNCHRONIZE | windows.FILE_READ_ATTRIBUTES,
.share_access = windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE,
.creation = windows.FILE_OPEN,
.case_sensitive = false,
},
) catch |err| {
// This fixed path should always be accessible on Windows.
std.debug.panic("unexpected error opening '\\??\\pipe\\': {t}", .{err});
};
errdefer _ = windows.ntdll.NtClose(pipe_device);
return .{
.pipe_device = pipe_device,
.pipe_path = pipe_path,
};
}
fn deinit(wp: *const Win32Pipe) void {
_ = windows.ntdll.NtClose(wp.pipe_device);
}
fn acquire(wp: *const Win32Pipe) AcquireError!Token {
const pipe_basename_offset = std.unicode.wtf8ToWtf16LeStringLiteral("\\??\\pipe\\").len;
const handle = while (true) {
if (windows.OpenFile(wp.pipe_path, .{
.access_mask = windows.SYNCHRONIZE,
.creation = windows.FILE_OPEN,
.share_access = 0,
.case_sensitive = false,
})) |handle| {
return .{ .win32pipe = handle };
} else |err| switch (err) {
error.PipeBusy, error.NoDevice => {},
error.IsDir,
error.FileNotFound,
error.NameTooLong,
error.AntivirusInterference,
error.BadPathName,
=> return error.ServerFailed,
error.AccessDenied,
error.Unexpected,
=> |e| return e,
error.NotDir => unreachable, // we're not opening as a directory
error.PathAlreadyExists => unreachable, // we're not trying to create the path
error.WouldBlock => unreachable, // we're not using overlapped I/O
error.NetworkNotFound => unreachable, // we're not accessing a network device
}
const fpwfb: windows.FILE_PIPE_WAIT_FOR_BUFFER = .init(
wp.pipe_path[pipe_basename_offset..],
windows.FILE_PIPE_WAIT_FOR_BUFFER.WAIT_FOREVER,
);
windows.DeviceIoControl(
wp.pipe_device,
windows.FSCTL.PIPE.WAIT,
.{ .in = fpwfb.toBuffer() },
) catch |err| switch (err) {
error.UnrecognizedVolume => unreachable, // not a volume
error.Pending => unreachable, // not using overlapped I/O
error.PipeClosing => return error.ServerFailed,
error.PipeAlreadyConnected => unreachable,
error.PipeAlreadyListening => unreachable,
error.Unexpected, error.AccessDenied => |e| return e,
};
continue;
};
return .{ .win32pipe = handle };
}
};
const builtin = @import("builtin");
const std = @import("../std.zig");
const Allocator = std.mem.Allocator;
const job = std.job;
const windows = std.os.windows;
const Client = @This();