| author | |
| committer | |
| log | b599ac5e82930c83b0d7232258c0c208a189c73e |
| tree | dde03d659db7ab557227f734eace0a254fa1215e |
| parent | 31a26bf483ea5cb59d362810f1095426b3aeeb25 |
5 files changed, 599 insertions(+), 0 deletions(-)
CMakeLists.txt+3| ... | @@ -449,6 +449,9 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -449,6 +449,9 @@ set(ZIG_STAGE2_SOURCES |
| 449 | lib/std/heap.zig | 449 | lib/std/heap.zig |
| 450 | lib/std/heap/arena_allocator.zig | 450 | lib/std/heap/arena_allocator.zig |
| 451 | lib/std/json.zig | 451 | lib/std/json.zig |
| 452 | lib/std/job.zig | ||
| 453 | lib/std/job/Client.zig | ||
| 454 | lib/std/job/Server.zig | ||
| 452 | lib/std/leb128.zig | 455 | lib/std/leb128.zig |
| 453 | lib/std/log.zig | 456 | lib/std/log.zig |
| 454 | lib/std/macho.zig | 457 | lib/std/macho.zig |
lib/std/job.zig created+116| ... | @@ -0,0 +1,116 @@ | ||
| 1 | //! This namespace provides an implementation of the Robust Jobserver protocol: | ||
| 2 | //! https://codeberg.org/mlugg/robust-jobserver/ | ||
| 3 | //! | ||
| 4 | //! `Client` and `Server` currently both support the `sysvsem` and `win32pipe` | ||
| 5 | //! communication methods, meaning this implementation is usable on most POSIX | ||
| 6 | //! targets and on Windows. | ||
| 7 | |||
| 8 | pub const Client = @import("job/Client.zig"); | ||
| 9 | pub const Server = @import("job/Server.zig"); | ||
| 10 | |||
| 11 | pub const Method = enum { sysvsem, win32pipe }; | ||
| 12 | |||
| 13 | pub const sysv_sem = struct { | ||
| 14 | pub const supported = switch (builtin.os.tag) { | ||
| 15 | .linux, | ||
| 16 | .illumos, | ||
| 17 | .haiku, | ||
| 18 | |||
| 19 | .freebsd, | ||
| 20 | .netbsd, | ||
| 21 | .openbsd, | ||
| 22 | .dragonfly, | ||
| 23 | |||
| 24 | .driverkit, | ||
| 25 | .ios, | ||
| 26 | .maccatalyst, | ||
| 27 | .macos, | ||
| 28 | .tvos, | ||
| 29 | .visionos, | ||
| 30 | .watchos, | ||
| 31 | => true, | ||
| 32 | |||
| 33 | else => false, | ||
| 34 | }; | ||
| 35 | |||
| 36 | /// `semget(IPC_PRIVATE, 1, 0o777)` | ||
| 37 | pub fn create() error{ SystemResources, Unexpected }!i32 { | ||
| 38 | const res = system.create(); | ||
| 39 | switch (std.posix.errno(res)) { | ||
| 40 | .SUCCESS => return @intCast(res), | ||
| 41 | .NOMEM => return error.SystemResources, | ||
| 42 | .NOSPC => return error.SystemResources, | ||
| 43 | else => |e| return std.posix.unexpectedErrno(e), | ||
| 44 | } | ||
| 45 | } | ||
| 46 | /// `semctl(id, 0, SETVAL, n)` | ||
| 47 | pub fn setValue(id: i32, n: u32) error{Unexpected}!void { | ||
| 48 | switch (std.posix.errno(system.setValue(id, n))) { | ||
| 49 | .SUCCESS => return, | ||
| 50 | else => |e| return std.posix.unexpectedErrno(e), | ||
| 51 | } | ||
| 52 | } | ||
| 53 | /// `semop(id, &.{.{ .sem_num = 0, .sem_op = delta, .sem_flg = SEM_UNDO }})` | ||
| 54 | pub fn modify(id: i32, delta: i16) error{ | ||
| 55 | InvalidSemaphore, | ||
| 56 | AccessDenied, | ||
| 57 | SystemResources, | ||
| 58 | /// A signal interrupted a blocked call to `modify`. | ||
| 59 | /// This allows the caller to implement cancelation. | ||
| 60 | Interrupted, | ||
| 61 | Unexpected, | ||
| 62 | }!void { | ||
| 63 | while (true) { | ||
| 64 | switch (std.posix.errno(system.modify(id, delta))) { | ||
| 65 | .SUCCESS => return, | ||
| 66 | .ACCES => return error.AccessDenied, | ||
| 67 | .FBIG => return error.InvalidSemaphore, | ||
| 68 | .IDRM => return error.InvalidSemaphore, | ||
| 69 | .INTR => return error.Interrupted, | ||
| 70 | .INVAL => return error.InvalidSemaphore, | ||
| 71 | .NOMEM => return error.SystemResources, | ||
| 72 | .RANGE => return error.InvalidSemaphore, | ||
| 73 | else => |e| return std.posix.unexpectedErrno(e), | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | const system = if (builtin.link_libc) struct { | ||
| 79 | fn create() c_int { | ||
| 80 | return std.c.semget(.IPC_PRIVATE, 1, 0o777); | ||
| 81 | } | ||
| 82 | fn setValue(id: i32, n: u32) c_int { | ||
| 83 | return std.c.semctl(id, 0, std.posix.SETVAL, n); | ||
| 84 | } | ||
| 85 | fn modify(id: i32, delta: i16) c_int { | ||
| 86 | var ops: [1]std.posix.sembuf = .{.{ | ||
| 87 | .sem_num = 0, | ||
| 88 | .sem_op = delta, | ||
| 89 | .sem_flg = std.posix.SEM_UNDO, | ||
| 90 | }}; | ||
| 91 | return std.c.semop(id, &ops, ops.len); | ||
| 92 | } | ||
| 93 | } else switch (builtin.os.tag) { | ||
| 94 | .linux => struct { | ||
| 95 | fn create() usize { | ||
| 96 | const key: std.posix.key_t = .IPC_PRIVATE; | ||
| 97 | return std.os.linux.syscall3(.semget, @intFromEnum(key), 1, 0o777); | ||
| 98 | } | ||
| 99 | fn setValue(id: i32, n: u32) usize { | ||
| 100 | return std.os.linux.syscall4(.semctl, @intCast(id), 0, std.posix.SETVAL, n); | ||
| 101 | } | ||
| 102 | fn modify(id: i32, delta: i16) usize { | ||
| 103 | var ops: [1]std.posix.sembuf = .{.{ | ||
| 104 | .sem_num = 0, | ||
| 105 | .sem_op = delta, | ||
| 106 | .sem_flg = std.posix.SEM_UNDO, | ||
| 107 | }}; | ||
| 108 | return std.os.linux.syscall3(.semop, @intCast(id), @intFromPtr(&ops), ops.len); | ||
| 109 | } | ||
| 110 | }, | ||
| 111 | else => unreachable, | ||
| 112 | }; | ||
| 113 | }; | ||
| 114 | |||
| 115 | const builtin = @import("builtin"); | ||
| 116 | const std = @import("std.zig"); | ||
lib/std/job/Client.zig created+197| ... | @@ -0,0 +1,197 @@ | ||
| 1 | impl: Impl, | ||
| 2 | |||
| 3 | pub const InitError = error{ | ||
| 4 | OutOfMemory, | ||
| 5 | /// There is no advertised job server. | ||
| 6 | NoServer, | ||
| 7 | /// The job server is advertising a communication method which is not known. | ||
| 8 | UnknownMethod, | ||
| 9 | /// The job server is advertising a communication method which is known but unsupported. | ||
| 10 | UnsupportedMethod, | ||
| 11 | /// A job server advertisement exists, but is malformed. | ||
| 12 | InvalidArgument, | ||
| 13 | /// The job server has shut down or is otherwise not available to connect to. | ||
| 14 | ServerFailed, | ||
| 15 | /// This process does not have permission to access the job server. | ||
| 16 | AccessDenied, | ||
| 17 | }; | ||
| 18 | pub fn init(arena: Allocator, env: *const std.process.EnvMap) InitError!Client { | ||
| 19 | const env_val = env.get("ROBUST_JOBSERVER") orelse return error.NoServer; | ||
| 20 | const idx = std.mem.findScalar(u8, env_val, ':') orelse return error.InvalidArgument; | ||
| 21 | const method = std.meta.stringToEnum(job.Method, env_val[0..idx]) orelse return error.UnknownMethod; | ||
| 22 | switch (method) { | ||
| 23 | inline else => |m| { | ||
| 24 | const ImplTy = @FieldType(Impl, @tagName(m)); | ||
| 25 | if (ImplTy == noreturn) return error.UnsupportedMethod; | ||
| 26 | return .{ .impl = @unionInit( | ||
| 27 | Impl, | ||
| 28 | @tagName(m), | ||
| 29 | try .init(arena, env_val[idx + 1 ..]), | ||
| 30 | ) }; | ||
| 31 | }, | ||
| 32 | } | ||
| 33 | } | ||
| 34 | pub fn deinit(c: *Client) void { | ||
| 35 | switch (c.impl) { | ||
| 36 | inline else => |*x| x.deinit(), | ||
| 37 | } | ||
| 38 | c.* = undefined; | ||
| 39 | } | ||
| 40 | |||
| 41 | pub const AcquireError = error{ | ||
| 42 | /// The job server has shut down or is otherwise not available to connect to. | ||
| 43 | ServerFailed, | ||
| 44 | /// This process does not have permission to access the job server. | ||
| 45 | AccessDenied, | ||
| 46 | /// Insufficient resources are available to acquire a token. | ||
| 47 | SystemResources, | ||
| 48 | Unexpected, | ||
| 49 | }; | ||
| 50 | pub fn acquire(c: *const Client) AcquireError!Token { | ||
| 51 | return switch (c.impl) { | ||
| 52 | inline else => |*impl| impl.acquire(), | ||
| 53 | }; | ||
| 54 | } | ||
| 55 | |||
| 56 | const Impl = union(job.Method) { | ||
| 57 | sysvsem: if (job.sysv_sem.supported) SysVSem else noreturn, | ||
| 58 | win32pipe: if (builtin.target.os.tag == .windows) Win32Pipe else noreturn, | ||
| 59 | }; | ||
| 60 | |||
| 61 | pub const Token = union(job.Method) { | ||
| 62 | sysvsem: if (job.sysv_sem.supported) SysVSem else noreturn, | ||
| 63 | win32pipe: if (builtin.target.os.tag == .windows) windows.HANDLE else noreturn, | ||
| 64 | pub fn release(t: Token) void { | ||
| 65 | switch (t) { | ||
| 66 | .sysvsem => |sem| while (true) { | ||
| 67 | return job.sysv_sem.modify(sem.set_id, 1) catch |err| switch (err) { | ||
| 68 | error.AccessDenied, error.InvalidSemaphore => { | ||
| 69 | // The semaphore broke somehow, but that's not our problem! | ||
| 70 | // (...at least, not until we next call `acquire`.) | ||
| 71 | }, | ||
| 72 | error.SystemResources => unreachable, // the undo structure was already allocated in `acquire` | ||
| 73 | error.Interrupted => continue, // releasing can't block; just retry | ||
| 74 | error.Unexpected => {}, // already warned, nothing more we can do | ||
| 75 | }; | ||
| 76 | }, | ||
| 77 | .win32pipe => |handle| _ = windows.ntdll.NtClose(handle), | ||
| 78 | } | ||
| 79 | } | ||
| 80 | }; | ||
| 81 | |||
| 82 | const SysVSem = struct { | ||
| 83 | set_id: i32, | ||
| 84 | fn init(arena: Allocator, arg: []const u8) InitError!SysVSem { | ||
| 85 | _ = arena; | ||
| 86 | const set_id = std.fmt.parseInt(i32, arg, 10) catch return error.InvalidArgument; | ||
| 87 | return .{ .set_id = set_id }; | ||
| 88 | } | ||
| 89 | fn deinit(sem: SysVSem) void { | ||
| 90 | _ = sem; | ||
| 91 | } | ||
| 92 | fn acquire(sem: SysVSem) AcquireError!Token { | ||
| 93 | while (true) { | ||
| 94 | break job.sysv_sem.modify(sem.set_id, -1) catch |err| switch (err) { | ||
| 95 | error.InvalidSemaphore => return error.ServerFailed, | ||
| 96 | error.Interrupted => continue, // TODO: support cancelation | ||
| 97 | error.AccessDenied, error.SystemResources, error.Unexpected => |e| return e, | ||
| 98 | }; | ||
| 99 | } | ||
| 100 | return .{ .sysvsem = sem }; | ||
| 101 | } | ||
| 102 | }; | ||
| 103 | |||
| 104 | const Win32Pipe = struct { | ||
| 105 | pipe_device: windows.HANDLE, | ||
| 106 | pipe_path: [:0]const u16, | ||
| 107 | fn init(arena: Allocator, arg: []const u8) InitError!Win32Pipe { | ||
| 108 | if (arg.len == 0) return error.InvalidArgument; | ||
| 109 | if (std.mem.findAny(u8, arg, "\\/\x00") != null) return error.InvalidArgument; | ||
| 110 | const pipe_path = std.unicode.wtf8ToWtf16LeAllocZ( | ||
| 111 | arena, | ||
| 112 | try std.fmt.allocPrint(arena, "\\??\\pipe\\{s}", .{arg}), | ||
| 113 | ) catch |err| switch (err) { | ||
| 114 | error.InvalidWtf8 => return error.InvalidArgument, | ||
| 115 | error.OutOfMemory => |e| return e, | ||
| 116 | }; | ||
| 117 | const pipe_device = windows.OpenFile( | ||
| 118 | std.unicode.wtf8ToWtf16LeStringLiteral("\\??\\pipe\\"), | ||
| 119 | .{ | ||
| 120 | .access_mask = .{ | ||
| 121 | .SPECIFIC = .{ .FILE_PIPE = .{ .READ_ATTRIBUTES = true } }, | ||
| 122 | .STANDARD = .{ .SYNCHRONIZE = true }, | ||
| 123 | }, | ||
| 124 | .share_access = .{ .READ = true, .WRITE = true }, | ||
| 125 | .creation = .OPEN, | ||
| 126 | }, | ||
| 127 | ) catch |err| { | ||
| 128 | // This fixed path should always be accessible on Windows. | ||
| 129 | std.debug.panic("unexpected error opening '\\??\\pipe\\': {t}", .{err}); | ||
| 130 | }; | ||
| 131 | errdefer _ = windows.ntdll.NtClose(pipe_device); | ||
| 132 | return .{ | ||
| 133 | .pipe_device = pipe_device, | ||
| 134 | .pipe_path = pipe_path, | ||
| 135 | }; | ||
| 136 | } | ||
| 137 | fn deinit(wp: *const Win32Pipe) void { | ||
| 138 | _ = windows.ntdll.NtClose(wp.pipe_device); | ||
| 139 | } | ||
| 140 | fn acquire(wp: *const Win32Pipe) AcquireError!Token { | ||
| 141 | const pipe_basename_offset = std.unicode.wtf8ToWtf16LeStringLiteral("\\??\\pipe\\").len; | ||
| 142 | const handle = while (true) { | ||
| 143 | if (windows.OpenFile(wp.pipe_path, .{ | ||
| 144 | .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } }, | ||
| 145 | .creation = .OPEN, | ||
| 146 | .share_access = .{}, | ||
| 147 | })) |handle| { | ||
| 148 | return .{ .win32pipe = handle }; | ||
| 149 | } else |err| switch (err) { | ||
| 150 | error.PipeBusy, error.NoDevice => {}, | ||
| 151 | |||
| 152 | error.IsDir, | ||
| 153 | error.FileNotFound, | ||
| 154 | error.NameTooLong, | ||
| 155 | error.AntivirusInterference, | ||
| 156 | error.BadPathName, | ||
| 157 | => return error.ServerFailed, | ||
| 158 | |||
| 159 | error.AccessDenied, | ||
| 160 | error.Unexpected, | ||
| 161 | => |e| return e, | ||
| 162 | |||
| 163 | error.NotDir => unreachable, // we're not opening as a directory | ||
| 164 | error.PathAlreadyExists => unreachable, // we're not trying to create the path | ||
| 165 | error.WouldBlock => unreachable, // we're not using overlapped I/O | ||
| 166 | error.NetworkNotFound => unreachable, // we're not accessing a network device | ||
| 167 | } | ||
| 168 | const fpwfb: windows.FILE.PIPE.WAIT_FOR_BUFFER = .init(.{ | ||
| 169 | .Timeout = windows.FILE.PIPE.WAIT_FOR_BUFFER.WAIT_FOREVER, | ||
| 170 | .Name = wp.pipe_path[pipe_basename_offset..], | ||
| 171 | }); | ||
| 172 | windows.DeviceIoControl( | ||
| 173 | wp.pipe_device, | ||
| 174 | windows.FSCTL.PIPE.WAIT, | ||
| 175 | .{ .in = fpwfb.toBuffer() }, | ||
| 176 | ) catch |err| switch (err) { | ||
| 177 | error.UnrecognizedVolume => unreachable, // not a volume | ||
| 178 | error.Pending => unreachable, // not using overlapped I/O | ||
| 179 | error.PipeClosing => return error.ServerFailed, | ||
| 180 | error.PipeAlreadyConnected => unreachable, | ||
| 181 | error.PipeAlreadyListening => unreachable, | ||
| 182 | error.Unexpected, error.AccessDenied => |e| return e, | ||
| 183 | }; | ||
| 184 | continue; | ||
| 185 | }; | ||
| 186 | return .{ .win32pipe = handle }; | ||
| 187 | } | ||
| 188 | }; | ||
| 189 | |||
| 190 | const builtin = @import("builtin"); | ||
| 191 | |||
| 192 | const std = @import("../std.zig"); | ||
| 193 | const Allocator = std.mem.Allocator; | ||
| 194 | const job = std.job; | ||
| 195 | const windows = std.os.windows; | ||
| 196 | |||
| 197 | const Client = @This(); | ||
lib/std/job/Server.zig created+282| ... | @@ -0,0 +1,282 @@ | ||
| 1 | /// `null` means we are inheriting a jobserver instance from a parent process. | ||
| 2 | impl: ?Impl, | ||
| 3 | |||
| 4 | pub const InitError = error{ OutOfMemory, SystemResources, Unexpected }; | ||
| 5 | pub fn init(arena: Allocator, job_limit: u32, env: *std.process.EnvMap) InitError!Server { | ||
| 6 | assert(job_limit > 0); | ||
| 7 | if (env.get("ROBUST_JOBSERVER") != null) { | ||
| 8 | return .{ .impl = null }; | ||
| 9 | } | ||
| 10 | const method: job.Method = switch (builtin.target.os.tag) { | ||
| 11 | .windows => .win32pipe, | ||
| 12 | else => .sysvsem, | ||
| 13 | }; | ||
| 14 | const impl: @FieldType(Impl, @tagName(method)) = try .init(arena, job_limit); | ||
| 15 | const env_val = try std.fmt.allocPrint(arena, "{t}:{f}", .{ method, std.fmt.alt(impl, .formatEnvData) }); | ||
| 16 | try env.put("ROBUST_JOBSERVER", env_val); | ||
| 17 | return .{ .impl = @unionInit(Impl, @tagName(method), impl) }; | ||
| 18 | } | ||
| 19 | pub fn deinit(s: *Server) void { | ||
| 20 | if (s.impl) |*impl| { | ||
| 21 | switch (impl.*) { | ||
| 22 | inline else => |*x| x.deinit(), | ||
| 23 | } | ||
| 24 | } | ||
| 25 | s.* = undefined; | ||
| 26 | } | ||
| 27 | |||
| 28 | const Impl = union(job.Method) { | ||
| 29 | sysvsem: if (job.sysv_sem.supported) SysVSem else noreturn, | ||
| 30 | win32pipe: if (builtin.target.os.tag == .windows) Win32Pipe else noreturn, | ||
| 31 | }; | ||
| 32 | |||
| 33 | const SysVSem = struct { | ||
| 34 | set_id: i32, | ||
| 35 | fn init(arena: Allocator, num_tokens: u32) InitError!SysVSem { | ||
| 36 | _ = arena; | ||
| 37 | const id = try job.sysv_sem.create(); | ||
| 38 | try job.sysv_sem.setValue(id, num_tokens); | ||
| 39 | return .{ .set_id = id }; | ||
| 40 | } | ||
| 41 | fn deinit(sem: SysVSem) void { | ||
| 42 | _ = sem; | ||
| 43 | } | ||
| 44 | pub fn formatEnvData(sem: SysVSem, w: *std.Io.Writer) std.Io.Writer.Error!void { | ||
| 45 | try w.print("{d}", .{sem.set_id}); | ||
| 46 | } | ||
| 47 | }; | ||
| 48 | |||
| 49 | const Win32Pipe = struct { | ||
| 50 | pipe_name: []const u8, | ||
| 51 | done_event: windows.HANDLE, | ||
| 52 | thread: std.Thread, | ||
| 53 | |||
| 54 | const Token = struct { | ||
| 55 | handle: windows.HANDLE, | ||
| 56 | iosb: windows.IO_STATUS_BLOCK, | ||
| 57 | dummy_read_buf: [1]u8, | ||
| 58 | }; | ||
| 59 | |||
| 60 | var pipe_name_counter: std.atomic.Value(u32) = .init(0); | ||
| 61 | fn init(arena: Allocator, num_tokens: u32) InitError!Win32Pipe { | ||
| 62 | const pipe_name = try std.fmt.allocPrint(arena, "zig-jobserver-{d}-{x}", .{ | ||
| 63 | windows.GetCurrentProcessId(), | ||
| 64 | std.crypto.random.int(u64), | ||
| 65 | }); | ||
| 66 | |||
| 67 | const nt_path = std.unicode.wtf8ToWtf16LeAllocZ( | ||
| 68 | arena, | ||
| 69 | try std.fmt.allocPrint(arena, "\\??\\pipe\\{s}", .{pipe_name}), | ||
| 70 | ) catch |err| switch (err) { | ||
| 71 | error.InvalidWtf8 => unreachable, | ||
| 72 | error.OutOfMemory => |e| return e, | ||
| 73 | }; | ||
| 74 | |||
| 75 | const tokens = try arena.alloc(Token, num_tokens); | ||
| 76 | @memset(tokens, .{ | ||
| 77 | .handle = windows.INVALID_HANDLE_VALUE, | ||
| 78 | .iosb = undefined, | ||
| 79 | .dummy_read_buf = undefined, | ||
| 80 | }); | ||
| 81 | errdefer for (tokens) |*token| { | ||
| 82 | if (token.handle != windows.INVALID_HANDLE_VALUE) { | ||
| 83 | _ = windows.ntdll.NtClose(token.handle); | ||
| 84 | } | ||
| 85 | }; | ||
| 86 | |||
| 87 | for (tokens) |*t| { | ||
| 88 | var path: windows.UNICODE_STRING = .{ | ||
| 89 | .Buffer = nt_path.ptr, | ||
| 90 | .Length = @intCast(@sizeOf(u16) * nt_path.len), | ||
| 91 | .MaximumLength = 0, | ||
| 92 | }; | ||
| 93 | var iosb: windows.IO_STATUS_BLOCK = undefined; | ||
| 94 | switch (windows.ntdll.NtCreateNamedPipeFile( | ||
| 95 | &t.handle, | ||
| 96 | .{ .GENERIC = .{ .READ = true } }, | ||
| 97 | &.{ | ||
| 98 | .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), | ||
| 99 | .RootDirectory = null, | ||
| 100 | .ObjectName = &path, | ||
| 101 | .Attributes = .{}, | ||
| 102 | .SecurityDescriptor = null, | ||
| 103 | .SecurityQualityOfService = null, | ||
| 104 | }, | ||
| 105 | &iosb, | ||
| 106 | .{ .WRITE = true }, | ||
| 107 | .OPEN_IF, | ||
| 108 | .{ .IO = .ASYNCHRONOUS }, | ||
| 109 | .{ .TYPE = .BYTE_STREAM }, | ||
| 110 | .{ .MODE = .BYTE_STREAM }, | ||
| 111 | .{ .OPERATION = .QUEUE }, | ||
| 112 | @intCast(num_tokens), | ||
| 113 | 0, | ||
| 114 | 0, | ||
| 115 | &((-120 * std.time.ns_per_s) / 100), | ||
| 116 | )) { | ||
| 117 | .SUCCESS => {}, | ||
| 118 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | ||
| 119 | else => |e| return windows.unexpectedStatus(e), | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | var done_event: windows.HANDLE = undefined; | ||
| 124 | switch (windows.ntdll.NtCreateEvent( | ||
| 125 | &done_event, | ||
| 126 | windows.ACCESS_MASK.Specific.Event.ALL_ACCESS, | ||
| 127 | null, | ||
| 128 | .Notification, | ||
| 129 | windows.FALSE, | ||
| 130 | )) { | ||
| 131 | .SUCCESS => {}, | ||
| 132 | .INSUFFICIENT_RESOURCES => return error.SystemResources, | ||
| 133 | else => |e| return windows.unexpectedStatus(e), | ||
| 134 | } | ||
| 135 | errdefer _ = windows.ntdll.NtClose(done_event); | ||
| 136 | |||
| 137 | const thread = std.Thread.spawn(.{}, serve, .{ tokens, done_event }) catch |err| switch (err) { | ||
| 138 | error.SystemResources, | ||
| 139 | error.Unexpected, | ||
| 140 | error.OutOfMemory, | ||
| 141 | => |e| return e, | ||
| 142 | |||
| 143 | error.ThreadQuotaExceeded, | ||
| 144 | error.LockedMemoryLimitExceeded, | ||
| 145 | => return error.SystemResources, | ||
| 146 | }; | ||
| 147 | errdefer comptime unreachable; // the thread is now running and owns `tokens` | ||
| 148 | |||
| 149 | return .{ | ||
| 150 | .pipe_name = pipe_name, | ||
| 151 | .done_event = done_event, | ||
| 152 | .thread = thread, | ||
| 153 | }; | ||
| 154 | } | ||
| 155 | fn deinit(wp: *const Win32Pipe) void { | ||
| 156 | _ = windows.ntdll.NtSetEvent(wp.done_event, null); | ||
| 157 | wp.thread.join(); | ||
| 158 | _ = windows.ntdll.NtClose(wp.done_event); | ||
| 159 | } | ||
| 160 | pub fn formatEnvData(wp: Win32Pipe, w: *std.Io.Writer) std.Io.Writer.Error!void { | ||
| 161 | try w.writeAll(wp.pipe_name); | ||
| 162 | } | ||
| 163 | |||
| 164 | fn serve(tokens: []Token, done_event: windows.HANDLE) void { | ||
| 165 | defer { | ||
| 166 | for (tokens) |*token| { | ||
| 167 | _ = windows.ntdll.NtClose(token.handle); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | for (tokens) |*t| serveToken(t, .connect); | ||
| 172 | |||
| 173 | while (true) { | ||
| 174 | switch (windows.ntdll.NtWaitForSingleObject( | ||
| 175 | done_event, | ||
| 176 | windows.TRUE, | ||
| 177 | null, | ||
| 178 | )) { | ||
| 179 | windows.NTSTATUS.ABANDONED_WAIT_0 => unreachable, // not a mutex | ||
| 180 | .USER_APC => continue, | ||
| 181 | windows.NTSTATUS.WAIT_0 => break, | ||
| 182 | .TIMEOUT => unreachable, // no timeout | ||
| 183 | else => |e| std.debug.panic("unexpected NTSTATUS=0x{x} in job server", .{@intFromEnum(e)}), | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | const Action = enum { connect, read, disconnect }; | ||
| 188 | fn serveToken(token: *Token, first_action: Action) void { | ||
| 189 | action: switch (first_action) { | ||
| 190 | .connect => if (windows.DeviceIoControl(token.handle, windows.FSCTL.PIPE.LISTEN, .{ | ||
| 191 | .apc_routine = &connectCompleted, | ||
| 192 | .apc_context = token, | ||
| 193 | .io_status_block = &token.iosb, | ||
| 194 | })) |_| { | ||
| 195 | return; // The APC has been queued and will continue the loop. | ||
| 196 | } else |err| switch (err) { | ||
| 197 | error.AccessDenied => unreachable, // we created the pipe | ||
| 198 | error.UnrecognizedVolume => unreachable, // it's not a volume | ||
| 199 | error.Pending => return, | ||
| 200 | error.PipeClosing => continue :action .disconnect, | ||
| 201 | error.PipeAlreadyConnected => continue :action .read, | ||
| 202 | error.PipeAlreadyListening => unreachable, // pipe is not in nonblocking mode | ||
| 203 | error.Unexpected => @panic("unexpected error in job server"), | ||
| 204 | }, | ||
| 205 | .read => switch (windows.ntdll.NtReadFile( | ||
| 206 | token.handle, | ||
| 207 | null, | ||
| 208 | &readCompleted, | ||
| 209 | token, | ||
| 210 | &token.iosb, | ||
| 211 | &token.dummy_read_buf, | ||
| 212 | token.dummy_read_buf.len, | ||
| 213 | null, | ||
| 214 | null, | ||
| 215 | )) { | ||
| 216 | .PENDING => return, | ||
| 217 | .PIPE_BROKEN => continue :action .disconnect, | ||
| 218 | .SUCCESS => { | ||
| 219 | // The client isn't meant to write to the pipe---disconnect them as punishment. | ||
| 220 | return; // The APC has been queued and will do that for us. | ||
| 221 | }, | ||
| 222 | else => |e| std.debug.panic("unexpected NTSTATUS=0x{x} in job server", .{@intFromEnum(e)}), | ||
| 223 | }, | ||
| 224 | .disconnect => if (windows.DeviceIoControl(token.handle, windows.FSCTL.PIPE.DISCONNECT, .{ | ||
| 225 | .apc_routine = &disconnectCompleted, | ||
| 226 | .apc_context = token, | ||
| 227 | .io_status_block = &token.iosb, | ||
| 228 | })) |_| { | ||
| 229 | return; // The APC has been queued and will continue the loop. | ||
| 230 | } else |err| switch (err) { | ||
| 231 | error.AccessDenied => unreachable, // we created the pipe | ||
| 232 | error.UnrecognizedVolume => unreachable, // it's not a volume | ||
| 233 | error.Pending => return, | ||
| 234 | error.PipeClosing => unreachable, | ||
| 235 | error.PipeAlreadyConnected => unreachable, | ||
| 236 | error.PipeAlreadyListening => unreachable, | ||
| 237 | error.Unexpected => @panic("unexpected error in job server"), | ||
| 238 | }, | ||
| 239 | } | ||
| 240 | } | ||
| 241 | fn connectCompleted( | ||
| 242 | ctx: ?*anyopaque, | ||
| 243 | iosb: *windows.IO_STATUS_BLOCK, | ||
| 244 | _: windows.ULONG, | ||
| 245 | ) callconv(.winapi) void { | ||
| 246 | serveToken(@ptrCast(@alignCast(ctx)), switch (iosb.u.Status) { | ||
| 247 | .SUCCESS, .PIPE_CONNECTED => .read, | ||
| 248 | .PIPE_CLOSING => .disconnect, | ||
| 249 | else => |e| std.debug.panic("unexpected NTSTATUS=0x{x} in job server", .{@intFromEnum(e)}), | ||
| 250 | }); | ||
| 251 | } | ||
| 252 | fn readCompleted( | ||
| 253 | ctx: ?*anyopaque, | ||
| 254 | iosb: *windows.IO_STATUS_BLOCK, | ||
| 255 | _: windows.ULONG, | ||
| 256 | ) callconv(.winapi) void { | ||
| 257 | serveToken(@ptrCast(@alignCast(ctx)), switch (iosb.u.Status) { | ||
| 258 | .SUCCESS, .PIPE_BROKEN => .disconnect, | ||
| 259 | else => |e| std.debug.panic("unexpected NTSTATUS=0x{x} in job server", .{@intFromEnum(e)}), | ||
| 260 | }); | ||
| 261 | } | ||
| 262 | fn disconnectCompleted( | ||
| 263 | ctx: ?*anyopaque, | ||
| 264 | iosb: *windows.IO_STATUS_BLOCK, | ||
| 265 | _: windows.ULONG, | ||
| 266 | ) callconv(.winapi) void { | ||
| 267 | serveToken(@ptrCast(@alignCast(ctx)), switch (iosb.u.Status) { | ||
| 268 | .SUCCESS => .connect, | ||
| 269 | else => |e| std.debug.panic("unexpected NTSTATUS=0x{x} in job server", .{@intFromEnum(e)}), | ||
| 270 | }); | ||
| 271 | } | ||
| 272 | }; | ||
| 273 | |||
| 274 | const builtin = @import("builtin"); | ||
| 275 | |||
| 276 | const std = @import("std"); | ||
| 277 | const Allocator = std.mem.Allocator; | ||
| 278 | const assert = std.debug.assert; | ||
| 279 | const job = std.job; | ||
| 280 | const windows = std.os.windows; | ||
| 281 | |||
| 282 | const Server = @This(); | ||
lib/std/std.zig+1| ... | @@ -78,6 +78,7 @@ pub const hash = @import("hash.zig"); | ... | @@ -78,6 +78,7 @@ pub const hash = @import("hash.zig"); |
| 78 | pub const hash_map = @import("hash_map.zig"); | 78 | pub const hash_map = @import("hash_map.zig"); |
| 79 | pub const heap = @import("heap.zig"); | 79 | pub const heap = @import("heap.zig"); |
| 80 | pub const http = @import("http.zig"); | 80 | pub const http = @import("http.zig"); |
| 81 | pub const job = @import("job.zig"); | ||
| 81 | pub const json = @import("json.zig"); | 82 | pub const json = @import("json.zig"); |
| 82 | pub const leb = @import("leb128.zig"); | 83 | pub const leb = @import("leb128.zig"); |
| 83 | pub const log = @import("log.zig"); | 84 | pub const log = @import("log.zig"); |