authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-20 17:05:14-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 22:03:13-08:00
logcb7be96644819e2e903c191e712d33b22f30a52a
tree2488dfeb47a58a5232f82dcde1da11559ec427f6
parentc7c4e8d802c5c8ab2dc9d064c3985836e6677115

std.Io: give File a nonblocking bit on Windows

This tracks whether it is a file opened in synchronous mode, or something that supports APC. This will be needed in order to know whether concurrent batch operations on the file should return error.ConcurrencyUnavailable, or use APC to complete the batch. This patch also switches to using NtCreateFile directly in std.Io.Threaded for dirCreateFile, as well as NtReadFile for fileReadStreaming, making it handle files opened in synchronous mode as well as files opened in asynchronous mode.

4 files changed, 100 insertions(+), 41 deletions(-)

lib/std/Io/File.zig+15
......@@ -10,8 +10,20 @@ const assert = std.debug.assert;
1010const Dir = std.Io.Dir;
1111
1212handle: Handle,
13flags: Flags = .{},
1314
1415pub const Handle = std.posix.fd_t;
16pub const Flags = switch (native_os) {
17 .windows => packed struct(u1) {
18 /// * true: opened with MODE.IO.ASYNCHRONOUS
19 /// * false: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or
20 /// not a file.
21 /// This is default-initialized to false as a workaround for
22 /// https://codeberg.org/ziglang/zig/issues/30842
23 nonblocking: bool = false,
24 },
25 else => packed struct(u0) {},
26};
1527
1628pub const Reader = @import("File/Reader.zig");
1729pub const Writer = @import("File/Writer.zig");
......@@ -77,6 +89,7 @@ pub fn stdout() File {
7789 return switch (native_os) {
7890 .windows => .{
7991 .handle = std.os.windows.peb().ProcessParameters.hStdOutput,
92 .flags = .{ .nonblocking = false },
8093 },
8194 else => .{
8295 .handle = std.posix.STDOUT_FILENO,
......@@ -88,6 +101,7 @@ pub fn stderr() File {
88101 return switch (native_os) {
89102 .windows => .{
90103 .handle = std.os.windows.peb().ProcessParameters.hStdError,
104 .flags = .{ .nonblocking = false },
91105 },
92106 else => .{
93107 .handle = std.posix.STDERR_FILENO,
......@@ -99,6 +113,7 @@ pub fn stdin() File {
99113 return switch (native_os) {
100114 .windows => .{
101115 .handle = std.os.windows.peb().ProcessParameters.hStdInput,
116 .flags = .{ .nonblocking = false },
102117 },
103118 else => .{
104119 .handle = std.posix.STDIN_FILENO,
lib/std/Io/Threaded.zig+83-40
......@@ -1173,6 +1173,11 @@ const Syscall = struct {
11731173 .blocked_canceling => return error.Canceled, // new status is `.canceled`
11741174 }
11751175 }
1176 fn toApc(s: Syscall) Io.Cancelable!void {
1177 // TODO set state to indicate instead of NtCancelSynchronousIoFile we
1178 // need to use NtCancelIoFileEx
1179 return s.checkCancel();
1180 }
11761181 /// Marks this syscall as finished.
11771182 fn finish(s: Syscall) void {
11781183 const thread = s.thread orelse return;
......@@ -2759,7 +2764,12 @@ fn dirCreateDirPathOpenWasi(
27592764
27602765fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
27612766 const t: *Threaded = @ptrCast(@alignCast(userdata));
2762 const file: File = .{ .handle = dir.handle };
2767 const file: File = if (is_windows) .{
2768 .handle = dir.handle,
2769 .flags = .{ .nonblocking = false },
2770 } else .{
2771 .handle = dir.handle,
2772 };
27632773 return fileStat(t, file);
27642774}
27652775
......@@ -3682,7 +3692,10 @@ fn dirCreateFileWindows(
36823692 errdefer windows.CloseHandle(handle);
36833693
36843694 const exclusive = switch (flags.lock) {
3685 .none => return .{ .handle = handle },
3695 .none => return .{
3696 .handle = handle,
3697 .flags = .{ .nonblocking = false },
3698 },
36863699 .shared => false,
36873700 .exclusive => true,
36883701 };
......@@ -3702,7 +3715,10 @@ fn dirCreateFileWindows(
37023715 )) {
37033716 .SUCCESS => {
37043717 syscall.finish();
3705 return .{ .handle = handle };
3718 return .{
3719 .handle = handle,
3720 .flags = .{ .nonblocking = false },
3721 };
37063722 },
37073723 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
37083724 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
......@@ -4273,7 +4289,10 @@ pub fn dirOpenFileWtf16(
42734289 errdefer w.CloseHandle(handle);
42744290
42754291 const exclusive = switch (flags.lock) {
4276 .none => return .{ .handle = handle },
4292 .none => return .{
4293 .handle = handle,
4294 .flags = .{ .nonblocking = false },
4295 },
42774296 .shared => false,
42784297 .exclusive => true,
42794298 };
......@@ -4296,7 +4315,10 @@ pub fn dirOpenFileWtf16(
42964315 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
42974316 else => |status| return syscall.unexpectedNtstatus(status),
42984317 };
4299 return .{ .handle = handle };
4318 return .{
4319 .handle = handle,
4320 .flags = .{ .nonblocking = false },
4321 };
43004322}
43014323
43024324fn dirOpenFileWasi(
......@@ -8365,46 +8387,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
83658387}
83668388
83678389fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
8368 const DWORD = windows.DWORD;
83698390 var index: usize = 0;
83708391 while (index < data.len and data[index].len == 0) index += 1;
83718392 if (index == data.len) return 0;
83728393 const buffer = data[index];
8373 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
83748394
8375 const syscall: Syscall = try .start();
8376 while (true) {
8377 var n: DWORD = undefined;
8378 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {
8379 syscall.finish();
8380 return n;
8395 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8396
8397 read: {
8398 const syscall: Syscall = try .start();
8399 while (true) {
8400 switch (windows.ntdll.NtReadFile(
8401 file.handle,
8402 null, // event
8403 noopApc, // apc callback
8404 null, // apc context
8405 &io_status_block,
8406 buffer.ptr,
8407 @min(std.math.maxInt(u32), buffer.len),
8408 null, // byte offset
8409 null, // key
8410 )) {
8411 .SUCCESS => break :read syscall.finish(),
8412 .PENDING => break,
8413 .CANCELLED => {
8414 try syscall.checkCancel();
8415 continue;
8416 },
8417 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // wrong value for flags.nonblocking
8418 else => |status| return syscall.unexpectedNtstatus(status),
8419 }
83818420 }
8382 switch (windows.GetLastError()) {
8383 .IO_PENDING => |err| {
8384 syscall.finish();
8385 return windows.errorBug(err);
8386 },
8387 .OPERATION_ABORTED => {
8388 try syscall.checkCancel();
8389 continue;
8390 },
8391 .BROKEN_PIPE, .HANDLE_EOF => {
8392 syscall.finish();
8393 return 0;
8394 },
8395 .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
8396 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8397 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8398 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
8399 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing
8400 // a handle to a directory.
8401 .INVALID_FUNCTION => return syscall.fail(error.IsDir),
8402 else => |err| {
8403 syscall.finish();
8404 return windows.unexpectedError(err);
8405 },
8421 try syscall.toApc();
8422 while (true) {
8423 switch (windows.ntdll.NtDelayExecution(1, null)) {
8424 .USER_APC => break syscall.finish(),
8425 .SUCCESS, .CANCELLED => {
8426 try syscall.checkCancel();
8427 continue;
8428 },
8429 else => |status| return syscall.unexpectedNtstatus(status),
8430 }
84068431 }
84078432 }
8433
8434 switch (io_status_block.u.Status) {
8435 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {},
8436 .ACCESS_DENIED => return error.AccessDenied,
8437 else => |status| return windows.unexpectedStatus(status),
8438 }
8439 return io_status_block.Information;
8440}
8441
8442fn noopApc(
8443 apc_context: ?*anyopaque,
8444 io_status_block: *windows.IO_STATUS_BLOCK,
8445 unused: windows.ULONG,
8446) callconv(.winapi) void {
8447 _ = apc_context;
8448 _ = io_status_block;
8449 _ = unused;
84088450}
84098451
84108452fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
......@@ -14560,9 +14602,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1456014602 return .{
1456114603 .id = piProcInfo.hProcess,
1456214604 .thread_handle = piProcInfo.hThread,
14563 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,
14564 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,
14565 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,
14605 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14606 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14607 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
1456614608 .request_resource_usage_statistics = options.request_resource_usage_statistics,
1456714609 };
1456814610}
......@@ -15696,6 +15738,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
1569615738 .pointer => @ptrFromInt(int),
1569715739 else => return error.UnsupportedOperation,
1569815740 },
15741 .flags = if (is_windows) .{ .nonblocking = true } else .{},
1569915742 };
1570015743}
1570115744
lib/std/Progress.zig+1
......@@ -979,6 +979,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
979979 if (main_parent == .unused) continue;
980980 const file: Io.File = .{
981981 .handle = main_storage.getIpcFd() orelse continue,
982 .flags = if (is_windows) .{ .nonblocking = true } else .{},
982983 };
983984 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
984985 var bytes_read: usize = 0;
lib/std/os/windows/ntdll.zig+1-1
......@@ -596,7 +596,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(
596596
597597pub extern "ntdll" fn NtDelayExecution(
598598 Alertable: BOOLEAN,
599 DelayInterval: *const LARGE_INTEGER,
599 DelayInterval: ?*const LARGE_INTEGER,
600600) callconv(.winapi) NTSTATUS;
601601
602602pub extern "ntdll" fn NtCancelIoFileEx(