Skip to content

Commit cb1d968

Browse files
committed
Implement Progress IPC for Windows (#20105)
1 parent ccf8488 commit cb1d968

File tree

4 files changed

+139
-12
lines changed

4 files changed

+139
-12
lines changed

lib/std/Progress.zig

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,12 @@ pub const Node = struct {
103103

104104
/// Thread-safe.
105105
fn setIpcFd(s: *Storage, fd: posix.fd_t) void {
106+
// NOTE: On Windows we end up casting a HANDLE (potential 64-bit opaque value) to i32,
107+
// but it seems the handle values are actually small values, so OK for now, but should perhaps
108+
// be handled better
106109
const integer: u32 = switch (@typeInfo(posix.fd_t)) {
107110
.int => @bitCast(fd),
108-
.pointer => @intFromPtr(fd),
111+
.pointer => @intCast(@intFromPtr(fd)),
109112
else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
110113
};
111114
// `estimated_total_count` max int indicates the special state that
@@ -263,10 +266,12 @@ pub const Node = struct {
263266
/// Posix-only. Used by `std.process.Child`. Thread-safe.
264267
pub fn setIpcFd(node: Node, fd: posix.fd_t) void {
265268
const index = node.index.unwrap() orelse return;
266-
assert(fd >= 0);
267-
assert(fd != posix.STDOUT_FILENO);
268-
assert(fd != posix.STDIN_FILENO);
269-
assert(fd != posix.STDERR_FILENO);
269+
if (!is_windows) {
270+
assert(fd >= 0);
271+
assert(fd != posix.STDOUT_FILENO);
272+
assert(fd != posix.STDIN_FILENO);
273+
assert(fd != posix.STDERR_FILENO);
274+
}
270275
storageByIndex(index).setIpcFd(fd);
271276
}
272277

@@ -379,7 +384,8 @@ pub fn start(options: Options) Node {
379384
if (noop_impl)
380385
return Node.none;
381386

382-
if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {
387+
const zp_env_type = if (is_windows) usize else u31;
388+
if (std.process.parseEnvVarInt("ZIG_PROGRESS", zp_env_type, 10)) |ipc_fd| {
383389
global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{
384390
@as(posix.fd_t, switch (@typeInfo(posix.fd_t)) {
385391
.int => ipc_fd,
@@ -890,6 +896,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
890896
}
891897
bytes_read += n;
892898
}
899+
893900
// Ignore all but the last message on the pipe.
894901
var input: []u8 = pipe_buf[0..bytes_read];
895902
if (input.len == 0) {

lib/std/os/windows.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usiz
631631
.OPERATION_ABORTED => continue,
632632
.BROKEN_PIPE => return 0,
633633
.HANDLE_EOF => return 0,
634+
.NO_DATA => return 0,
634635
.NETNAME_DELETED => return error.ConnectionResetByPeer,
635636
.LOCK_VIOLATION => return error.LockViolation,
636637
else => |err| return unexpectedError(err),

lib/std/process.zig

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1831,7 +1831,7 @@ pub const CreateEnvironOptions = struct {
18311831
/// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified.
18321832
/// If non-null, negative means to remove the environment variable, and >= 0
18331833
/// means to provide it with the given integer.
1834-
zig_progress_fd: ?i32 = null,
1834+
zig_progress_fd: if (native_os == .windows) ?usize else ?i32 = null,
18351835
};
18361836

18371837
/// Creates a null-delimited environment variable block in the format
@@ -1997,7 +1997,22 @@ test createNullDelimitedEnvMap {
19971997
}
19981998

19991999
/// Caller must free result.
2000-
pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 {
2000+
pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap, options: CreateEnvironOptions) ![]u16 {
2001+
var zig_progress_value: ?[]const u8 = env_map.get("ZIG_PROGRESS");
2002+
2003+
const ZigProgressAction = enum { nothing, edit, delete, add };
2004+
const zig_progress_action: ZigProgressAction = a: {
2005+
const fd = options.zig_progress_fd orelse break :a .nothing;
2006+
const contains = zig_progress_value != null;
2007+
if (fd >= 0) {
2008+
zig_progress_value = try std.fmt.allocPrintZ(allocator, "{d}", .{fd});
2009+
break :a if (contains) .edit else .add;
2010+
} else {
2011+
if (contains) break :a .delete;
2012+
}
2013+
break :a .nothing;
2014+
};
2015+
20012016
// count bytes needed
20022017
const max_chars_needed = x: {
20032018
var max_chars_needed: usize = 4; // 4 for the final 4 null bytes
@@ -2007,18 +2022,41 @@ pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) !
20072022
// +1 for null byte
20082023
max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2;
20092024
}
2025+
switch (zig_progress_action) {
2026+
.add => max_chars_needed += "ZIG_PROGRESS".len + (zig_progress_value.?).len + 2,
2027+
.delete, .nothing, .edit => {},
2028+
}
20102029
break :x max_chars_needed;
20112030
};
20122031
const result = try allocator.alloc(u16, max_chars_needed);
20132032
errdefer allocator.free(result);
20142033

20152034
var it = env_map.iterator();
20162035
var i: usize = 0;
2036+
2037+
if (zig_progress_action == .add) {
2038+
i += try unicode.wtf8ToWtf16Le(result[i..], "ZIG_PROGRESS");
2039+
result[i] = '=';
2040+
i += 1;
2041+
i += try unicode.wtf8ToWtf16Le(result[i..], zig_progress_value.?);
2042+
result[i] = 0;
2043+
i += 1;
2044+
}
2045+
20172046
while (it.next()) |pair| {
2047+
var value = pair.value_ptr.*;
2048+
if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) {
2049+
.add => unreachable,
2050+
.delete => continue,
2051+
.edit => {
2052+
value = zig_progress_value.?;
2053+
},
2054+
.nothing => {},
2055+
};
20182056
i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*);
20192057
result[i] = '=';
20202058
i += 1;
2021-
i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*);
2059+
i += try unicode.wtf8ToWtf16Le(result[i..], value);
20222060
result[i] = 0;
20232061
i += 1;
20242062
}

lib/std/process/Child.zig

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,12 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
820820
windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr);
821821
};
822822

823+
var prog_pipe_rd: ?windows.HANDLE = null;
824+
var prog_pipe_wr: ?windows.HANDLE = null;
825+
if (self.progress_node.index != .none) {
826+
try windowsMakeProgressPipe(&prog_pipe_rd, &prog_pipe_wr, &saAttr);
827+
errdefer windowsDestroyPipe(prog_pipe_rd, prog_pipe_wr);
828+
}
823829
var siStartInfo = windows.STARTUPINFOW{
824830
.cb = @sizeOf(windows.STARTUPINFOW),
825831
.hStdError = g_hChildStd_ERR_Wr,
@@ -847,9 +853,19 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
847853
defer if (cwd_w) |cwd| self.allocator.free(cwd);
848854
const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null;
849855

850-
const maybe_envp_buf = if (self.env_map) |env_map| try process.createWindowsEnvBlock(self.allocator, env_map) else null;
851-
defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
852-
const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
856+
const maybe_envp_buf = m: {
857+
const prog_fd: ?usize = if (prog_pipe_wr) |h| @intFromPtr(h) else null;
858+
if (self.env_map) |env_map| {
859+
break :m try process.createWindowsEnvBlock(self.allocator, env_map, .{ .zig_progress_fd = prog_fd });
860+
} else {
861+
const env_map = try process.getEnvMap(self.allocator);
862+
break :m try process.createWindowsEnvBlock(self.allocator, &env_map, .{ .zig_progress_fd = prog_fd });
863+
}
864+
};
865+
// defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf);
866+
// const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null;
867+
defer self.allocator.free(maybe_envp_buf);
868+
const envp_ptr = maybe_envp_buf.ptr;
853869

854870
const app_name_wtf8 = self.argv[0];
855871
const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8);
@@ -998,6 +1014,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
9981014
if (self.stdout_behavior == StdIo.Pipe) {
9991015
posix.close(g_hChildStd_OUT_Wr.?);
10001016
}
1017+
if (prog_pipe_rd) |fd| self.progress_node.setIpcFd(fd);
10011018
}
10021019

10031020
fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
@@ -1394,6 +1411,70 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons
13941411
wr.* = write_handle;
13951412
}
13961413

1414+
fn windowsMakeProgressPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void {
1415+
var tmp_bufw: [128]u16 = undefined;
1416+
1417+
// NOTE: Only difference with windowsMakeAsyncPipe now is windows.PIPE_NOWAIT flag and
1418+
// pipe name
1419+
1420+
// Anonymous pipes are built upon Named pipes.
1421+
// https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe
1422+
// Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
1423+
// https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations
1424+
const pipe_path = blk: {
1425+
var tmp_buf: [128]u8 = undefined;
1426+
// Forge a random path for the pipe.
1427+
const pipe_path = std.fmt.bufPrintZ(
1428+
&tmp_buf,
1429+
"\\\\.\\pipe\\zig-progress-{d}-{d}",
1430+
.{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
1431+
) catch unreachable;
1432+
const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
1433+
tmp_bufw[len] = 0;
1434+
break :blk tmp_bufw[0..len :0];
1435+
};
1436+
1437+
// Create the read handle that can be used with overlapped IO ops.
1438+
const read_handle = windows.kernel32.CreateNamedPipeW(
1439+
pipe_path.ptr,
1440+
windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED,
1441+
windows.PIPE_TYPE_BYTE | windows.PIPE_NOWAIT,
1442+
1,
1443+
4096,
1444+
4096,
1445+
0,
1446+
sattr,
1447+
);
1448+
if (read_handle == windows.INVALID_HANDLE_VALUE) {
1449+
switch (windows.GetLastError()) {
1450+
else => |err| return windows.unexpectedError(err),
1451+
}
1452+
}
1453+
errdefer posix.close(read_handle);
1454+
1455+
var sattr_copy = sattr.*;
1456+
const write_handle = windows.kernel32.CreateFileW(
1457+
pipe_path.ptr,
1458+
windows.GENERIC_WRITE,
1459+
0,
1460+
&sattr_copy,
1461+
windows.OPEN_EXISTING,
1462+
windows.FILE_ATTRIBUTE_NORMAL,
1463+
null,
1464+
);
1465+
if (write_handle == windows.INVALID_HANDLE_VALUE) {
1466+
switch (windows.GetLastError()) {
1467+
else => |err| return windows.unexpectedError(err),
1468+
}
1469+
}
1470+
errdefer posix.close(write_handle);
1471+
1472+
try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0);
1473+
1474+
rd.* = read_handle;
1475+
wr.* = write_handle;
1476+
}
1477+
13971478
var pipe_name_counter = std.atomic.Value(u32).init(1);
13981479

13991480
/// File name extensions supported natively by `CreateProcess()` on Windows.

0 commit comments

Comments
 (0)