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 12:10:00-08:00
log59ebd82ebf4746e47b3e84367b1ea87bbec7d222
treecc2d47e76d2c380aaf0c1c12039a1af8f6b04970
parentfa988e88ed21485830a70276b5c7567efb122f80

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;...@@ -10,8 +10,20 @@ const assert = std.debug.assert;
10const Dir = std.Io.Dir;10const Dir = std.Io.Dir;
1111
12handle: Handle,12handle: Handle,
13flags: Flags = .{},
1314
14pub const Handle = std.posix.fd_t;15pub 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
16pub const Reader = @import("File/Reader.zig");28pub const Reader = @import("File/Reader.zig");
17pub const Writer = @import("File/Writer.zig");29pub const Writer = @import("File/Writer.zig");
...@@ -77,6 +89,7 @@ pub fn stdout() File {...@@ -77,6 +89,7 @@ pub fn stdout() File {
77 return switch (native_os) {89 return switch (native_os) {
78 .windows => .{90 .windows => .{
79 .handle = std.os.windows.peb().ProcessParameters.hStdOutput,91 .handle = std.os.windows.peb().ProcessParameters.hStdOutput,
92 .flags = .{ .nonblocking = false },
80 },93 },
81 else => .{94 else => .{
82 .handle = std.posix.STDOUT_FILENO,95 .handle = std.posix.STDOUT_FILENO,
...@@ -88,6 +101,7 @@ pub fn stderr() File {...@@ -88,6 +101,7 @@ pub fn stderr() File {
88 return switch (native_os) {101 return switch (native_os) {
89 .windows => .{102 .windows => .{
90 .handle = std.os.windows.peb().ProcessParameters.hStdError,103 .handle = std.os.windows.peb().ProcessParameters.hStdError,
104 .flags = .{ .nonblocking = false },
91 },105 },
92 else => .{106 else => .{
93 .handle = std.posix.STDERR_FILENO,107 .handle = std.posix.STDERR_FILENO,
...@@ -99,6 +113,7 @@ pub fn stdin() File {...@@ -99,6 +113,7 @@ pub fn stdin() File {
99 return switch (native_os) {113 return switch (native_os) {
100 .windows => .{114 .windows => .{
101 .handle = std.os.windows.peb().ProcessParameters.hStdInput,115 .handle = std.os.windows.peb().ProcessParameters.hStdInput,
116 .flags = .{ .nonblocking = false },
102 },117 },
103 else => .{118 else => .{
104 .handle = std.posix.STDIN_FILENO,119 .handle = std.posix.STDIN_FILENO,
lib/std/Io/Threaded.zig+83-40
...@@ -1173,6 +1173,11 @@ const Syscall = struct {...@@ -1173,6 +1173,11 @@ const Syscall = struct {
1173 .blocked_canceling => return error.Canceled, // new status is `.canceled`1173 .blocked_canceling => return error.Canceled, // new status is `.canceled`
1174 }1174 }
1175 }1175 }
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 }
1176 /// Marks this syscall as finished.1181 /// Marks this syscall as finished.
1177 fn finish(s: Syscall) void {1182 fn finish(s: Syscall) void {
1178 const thread = s.thread orelse return;1183 const thread = s.thread orelse return;
...@@ -2759,7 +2764,12 @@ fn dirCreateDirPathOpenWasi(...@@ -2759,7 +2764,12 @@ fn dirCreateDirPathOpenWasi(
27592764
2760fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {2765fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
2761 const t: *Threaded = @ptrCast(@alignCast(userdata));2766 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 };
2763 return fileStat(t, file);2773 return fileStat(t, file);
2764}2774}
27652775
...@@ -3682,7 +3692,10 @@ fn dirCreateFileWindows(...@@ -3682,7 +3692,10 @@ fn dirCreateFileWindows(
3682 errdefer windows.CloseHandle(handle);3692 errdefer windows.CloseHandle(handle);
36833693
3684 const exclusive = switch (flags.lock) {3694 const exclusive = switch (flags.lock) {
3685 .none => return .{ .handle = handle },3695 .none => return .{
3696 .handle = handle,
3697 .flags = .{ .nonblocking = false },
3698 },
3686 .shared => false,3699 .shared => false,
3687 .exclusive => true,3700 .exclusive => true,
3688 };3701 };
...@@ -3702,7 +3715,10 @@ fn dirCreateFileWindows(...@@ -3702,7 +3715,10 @@ fn dirCreateFileWindows(
3702 )) {3715 )) {
3703 .SUCCESS => {3716 .SUCCESS => {
3704 syscall.finish();3717 syscall.finish();
3705 return .{ .handle = handle };3718 return .{
3719 .handle = handle,
3720 .flags = .{ .nonblocking = false },
3721 };
3706 },3722 },
3707 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),3723 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3708 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),3724 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
...@@ -4273,7 +4289,10 @@ pub fn dirOpenFileWtf16(...@@ -4273,7 +4289,10 @@ pub fn dirOpenFileWtf16(
4273 errdefer w.CloseHandle(handle);4289 errdefer w.CloseHandle(handle);
42744290
4275 const exclusive = switch (flags.lock) {4291 const exclusive = switch (flags.lock) {
4276 .none => return .{ .handle = handle },4292 .none => return .{
4293 .handle = handle,
4294 .flags = .{ .nonblocking = false },
4295 },
4277 .shared => false,4296 .shared => false,
4278 .exclusive => true,4297 .exclusive => true,
4279 };4298 };
...@@ -4296,7 +4315,10 @@ pub fn dirOpenFileWtf16(...@@ -4296,7 +4315,10 @@ pub fn dirOpenFileWtf16(
4296 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer4315 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
4297 else => |status| return syscall.unexpectedNtstatus(status),4316 else => |status| return syscall.unexpectedNtstatus(status),
4298 };4317 };
4299 return .{ .handle = handle };4318 return .{
4319 .handle = handle,
4320 .flags = .{ .nonblocking = false },
4321 };
4300}4322}
43014323
4302fn dirOpenFileWasi(4324fn dirOpenFileWasi(
...@@ -8365,46 +8387,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz...@@ -8365,46 +8387,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
8365}8387}
83668388
8367fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {8389fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
8368 const DWORD = windows.DWORD;
8369 var index: usize = 0;8390 var index: usize = 0;
8370 while (index < data.len and data[index].len == 0) index += 1;8391 while (index < data.len and data[index].len == 0) index += 1;
8371 if (index == data.len) return 0;8392 if (index == data.len) return 0;
8372 const buffer = data[index];8393 const buffer = data[index];
8373 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
83748394
8375 const syscall: Syscall = try .start();8395 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8376 while (true) {8396
8377 var n: DWORD = undefined;8397 read: {
8378 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {8398 const syscall: Syscall = try .start();
8379 syscall.finish();8399 while (true) {
8380 return n;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 }
8381 }8420 }
8382 switch (windows.GetLastError()) {8421 try syscall.toApc();
8383 .IO_PENDING => |err| {8422 while (true) {
8384 syscall.finish();8423 switch (windows.ntdll.NtDelayExecution(1, null)) {
8385 return windows.errorBug(err);8424 .USER_APC => break syscall.finish(),
8386 },8425 .SUCCESS, .CANCELLED => {
8387 .OPERATION_ABORTED => {8426 try syscall.checkCancel();
8388 try syscall.checkCancel();8427 continue;
8389 continue;8428 },
8390 },8429 else => |status| return syscall.unexpectedNtstatus(status),
8391 .BROKEN_PIPE, .HANDLE_EOF => {8430 }
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 },
8406 }8431 }
8407 }8432 }
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;
8408}8450}
84098451
8410fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {8452fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
...@@ -14560,9 +14602,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -14560,9 +14602,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
14560 return .{14602 return .{
14561 .id = piProcInfo.hProcess,14603 .id = piProcInfo.hProcess,
14562 .thread_handle = piProcInfo.hThread,14604 .thread_handle = piProcInfo.hThread,
14563 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,14605 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14564 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,14606 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14565 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,14607 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14566 .request_resource_usage_statistics = options.request_resource_usage_statistics,14608 .request_resource_usage_statistics = options.request_resource_usage_statistics,
14567 };14609 };
14568}14610}
...@@ -15696,6 +15738,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -15696,6 +15738,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
15696 .pointer => @ptrFromInt(int),15738 .pointer => @ptrFromInt(int),
15697 else => return error.UnsupportedOperation,15739 else => return error.UnsupportedOperation,
15698 },15740 },
15741 .flags = if (is_windows) .{ .nonblocking = true } else .{},
15699 };15742 };
15700}15743}
1570115744
lib/std/Progress.zig+1
...@@ -979,6 +979,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -979,6 +979,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
979 if (main_parent == .unused) continue;979 if (main_parent == .unused) continue;
980 const file: Io.File = .{980 const file: Io.File = .{
981 .handle = main_storage.getIpcFd() orelse continue,981 .handle = main_storage.getIpcFd() orelse continue,
982 .flags = if (is_windows) .{ .nonblocking = true } else .{},
982 };983 };
983 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);984 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
984 var bytes_read: usize = 0;985 var bytes_read: usize = 0;
lib/std/os/windows/ntdll.zig+1-1
...@@ -596,7 +596,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(...@@ -596,7 +596,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(
596596
597pub extern "ntdll" fn NtDelayExecution(597pub extern "ntdll" fn NtDelayExecution(
598 Alertable: BOOLEAN,598 Alertable: BOOLEAN,
599 DelayInterval: *const LARGE_INTEGER,599 DelayInterval: ?*const LARGE_INTEGER,
600) callconv(.winapi) NTSTATUS;600) callconv(.winapi) NTSTATUS;
601601
602pub extern "ntdll" fn NtCancelIoFileEx(602pub extern "ntdll" fn NtCancelIoFileEx(