-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Progress IPC for Windows (#20105) #22000
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1831,7 +1831,7 @@ pub const CreateEnvironOptions = struct { | |
/// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. | ||
/// If non-null, negative means to remove the environment variable, and >= 0 | ||
/// means to provide it with the given integer. | ||
zig_progress_fd: ?i32 = null, | ||
zig_progress_fd: if (native_os == .windows) ?usize else ?i32 = null, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using usize for windows excludes possibility of negative number here.. not sure about the consequences of that yet. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it very sketchy to use just a negative number to mean something special for one particular function? Should probably be handled with an enum or something? |
||
}; | ||
|
||
/// Creates a null-delimited environment variable block in the format | ||
|
@@ -1997,7 +1997,22 @@ test createNullDelimitedEnvMap { | |
} | ||
|
||
/// Caller must free result. | ||
pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u16 { | ||
pub fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap, options: CreateEnvironOptions) ![]u16 { | ||
var zig_progress_value: ?[]const u8 = env_map.get("ZIG_PROGRESS"); | ||
|
||
const ZigProgressAction = enum { nothing, edit, delete, add }; | ||
const zig_progress_action: ZigProgressAction = a: { | ||
const fd = options.zig_progress_fd orelse break :a .nothing; | ||
const contains = zig_progress_value != null; | ||
if (fd >= 0) { | ||
zig_progress_value = try std.fmt.allocPrintZ(allocator, "{d}", .{fd}); | ||
break :a if (contains) .edit else .add; | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the else case might never happen on Windows since we use "usize" for fd |
||
if (contains) break :a .delete; | ||
} | ||
break :a .nothing; | ||
}; | ||
|
||
// count bytes needed | ||
const max_chars_needed = x: { | ||
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) ! | |
// +1 for null byte | ||
max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; | ||
} | ||
switch (zig_progress_action) { | ||
.add => max_chars_needed += "ZIG_PROGRESS".len + (zig_progress_value.?).len + 2, | ||
.delete, .nothing, .edit => {}, | ||
} | ||
break :x max_chars_needed; | ||
}; | ||
const result = try allocator.alloc(u16, max_chars_needed); | ||
errdefer allocator.free(result); | ||
|
||
var it = env_map.iterator(); | ||
var i: usize = 0; | ||
|
||
if (zig_progress_action == .add) { | ||
i += try unicode.wtf8ToWtf16Le(result[i..], "ZIG_PROGRESS"); | ||
result[i] = '='; | ||
i += 1; | ||
i += try unicode.wtf8ToWtf16Le(result[i..], zig_progress_value.?); | ||
result[i] = 0; | ||
i += 1; | ||
} | ||
|
||
while (it.next()) |pair| { | ||
var value = pair.value_ptr.*; | ||
if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { | ||
.add => unreachable, | ||
.delete => continue, | ||
.edit => { | ||
value = zig_progress_value.?; | ||
}, | ||
.nothing => {}, | ||
}; | ||
i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); | ||
result[i] = '='; | ||
i += 1; | ||
i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); | ||
i += try unicode.wtf8ToWtf16Le(result[i..], value); | ||
result[i] = 0; | ||
i += 1; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -820,6 +820,12 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { | |
windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); | ||
}; | ||
|
||
var prog_pipe_rd: ?windows.HANDLE = null; | ||
var prog_pipe_wr: ?windows.HANDLE = null; | ||
if (self.progress_node.index != .none) { | ||
try windowsMakeProgressPipe(&prog_pipe_rd, &prog_pipe_wr, &saAttr); | ||
errdefer windowsDestroyPipe(prog_pipe_rd, prog_pipe_wr); | ||
} | ||
var siStartInfo = windows.STARTUPINFOW{ | ||
.cb = @sizeOf(windows.STARTUPINFOW), | ||
.hStdError = g_hChildStd_ERR_Wr, | ||
|
@@ -847,9 +853,19 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { | |
defer if (cwd_w) |cwd| self.allocator.free(cwd); | ||
const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; | ||
|
||
const maybe_envp_buf = if (self.env_map) |env_map| try process.createWindowsEnvBlock(self.allocator, env_map) else null; | ||
defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf); | ||
const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; | ||
const maybe_envp_buf = m: { | ||
const prog_fd: ?usize = if (prog_pipe_wr) |h| @intFromPtr(h) else null; | ||
if (self.env_map) |env_map| { | ||
break :m try process.createWindowsEnvBlock(self.allocator, env_map, .{ .zig_progress_fd = prog_fd }); | ||
} else { | ||
const env_map = try process.getEnvMap(self.allocator); | ||
break :m try process.createWindowsEnvBlock(self.allocator, &env_map, .{ .zig_progress_fd = prog_fd }); | ||
} | ||
}; | ||
// defer if (maybe_envp_buf) |envp_buf| self.allocator.free(envp_buf); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of the cases above return null, so "maybe_envp_buf" ends up not being an optional... |
||
// const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; | ||
defer self.allocator.free(maybe_envp_buf); | ||
const envp_ptr = maybe_envp_buf.ptr; | ||
|
||
const app_name_wtf8 = self.argv[0]; | ||
const app_name_is_absolute = fs.path.isAbsolute(app_name_wtf8); | ||
|
@@ -998,6 +1014,7 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { | |
if (self.stdout_behavior == StdIo.Pipe) { | ||
posix.close(g_hChildStd_OUT_Wr.?); | ||
} | ||
if (prog_pipe_rd) |fd| self.progress_node.setIpcFd(fd); | ||
} | ||
|
||
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 | |
wr.* = write_handle; | ||
} | ||
|
||
fn windowsMakeProgressPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to use windows.CreatePipe to avoid kernel32 calls (I saw #1840) but wasn't able to make that work in my testcase. |
||
var tmp_bufw: [128]u16 = undefined; | ||
|
||
// NOTE: Only difference with windowsMakeAsyncPipe now is windows.PIPE_NOWAIT flag and | ||
// pipe name | ||
|
||
// Anonymous pipes are built upon Named pipes. | ||
// https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe | ||
// Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. | ||
// https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations | ||
const pipe_path = blk: { | ||
var tmp_buf: [128]u8 = undefined; | ||
// Forge a random path for the pipe. | ||
const pipe_path = std.fmt.bufPrintZ( | ||
&tmp_buf, | ||
"\\\\.\\pipe\\zig-progress-{d}-{d}", | ||
.{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) }, | ||
) catch unreachable; | ||
const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable; | ||
tmp_bufw[len] = 0; | ||
break :blk tmp_bufw[0..len :0]; | ||
}; | ||
|
||
// Create the read handle that can be used with overlapped IO ops. | ||
const read_handle = windows.kernel32.CreateNamedPipeW( | ||
pipe_path.ptr, | ||
windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, | ||
windows.PIPE_TYPE_BYTE | windows.PIPE_NOWAIT, | ||
1, | ||
4096, | ||
4096, | ||
0, | ||
sattr, | ||
); | ||
if (read_handle == windows.INVALID_HANDLE_VALUE) { | ||
switch (windows.GetLastError()) { | ||
else => |err| return windows.unexpectedError(err), | ||
} | ||
} | ||
errdefer posix.close(read_handle); | ||
|
||
var sattr_copy = sattr.*; | ||
const write_handle = windows.kernel32.CreateFileW( | ||
pipe_path.ptr, | ||
windows.GENERIC_WRITE, | ||
0, | ||
&sattr_copy, | ||
windows.OPEN_EXISTING, | ||
windows.FILE_ATTRIBUTE_NORMAL, | ||
null, | ||
); | ||
if (write_handle == windows.INVALID_HANDLE_VALUE) { | ||
switch (windows.GetLastError()) { | ||
else => |err| return windows.unexpectedError(err), | ||
} | ||
} | ||
errdefer posix.close(write_handle); | ||
|
||
try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); | ||
|
||
rd.* = read_handle; | ||
wr.* = write_handle; | ||
} | ||
|
||
var pipe_name_counter = std.atomic.Value(u32).init(1); | ||
|
||
/// File name extensions supported natively by `CreateProcess()` on Windows. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When windows.PIPE_NOWAIT was added to the pipe used for reading, I got the NO_DATA error here.
I was kind of guessing that it would be correct to return 0 here in this case..