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-27 15:32:34-08:00
logde3a2c0ebb60901f7603af0fbfa0a57b27a4b5a5
treeb7cdd155ed00710643e5ff8f0090e11585aa69b0
parent0f51f663f06728f38f518073a23d69a7c1b0d792

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;
...@@ -2754,7 +2759,12 @@ fn dirCreateDirPathOpenWasi(...@@ -2754,7 +2759,12 @@ fn dirCreateDirPathOpenWasi(
27542759
2755fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {2760fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
2756 const t: *Threaded = @ptrCast(@alignCast(userdata));2761 const t: *Threaded = @ptrCast(@alignCast(userdata));
2757 const file: File = .{ .handle = dir.handle };2762 const file: File = if (is_windows) .{
2763 .handle = dir.handle,
2764 .flags = .{ .nonblocking = false },
2765 } else .{
2766 .handle = dir.handle,
2767 };
2758 return fileStat(t, file);2768 return fileStat(t, file);
2759}2769}
27602770
...@@ -3676,7 +3686,10 @@ fn dirCreateFileWindows(...@@ -3676,7 +3686,10 @@ fn dirCreateFileWindows(
3676 errdefer windows.CloseHandle(handle);3686 errdefer windows.CloseHandle(handle);
36773687
3678 const exclusive = switch (flags.lock) {3688 const exclusive = switch (flags.lock) {
3679 .none => return .{ .handle = handle },3689 .none => return .{
3690 .handle = handle,
3691 .flags = .{ .nonblocking = false },
3692 },
3680 .shared => false,3693 .shared => false,
3681 .exclusive => true,3694 .exclusive => true,
3682 };3695 };
...@@ -3696,7 +3709,10 @@ fn dirCreateFileWindows(...@@ -3696,7 +3709,10 @@ fn dirCreateFileWindows(
3696 )) {3709 )) {
3697 .SUCCESS => {3710 .SUCCESS => {
3698 syscall.finish();3711 syscall.finish();
3699 return .{ .handle = handle };3712 return .{
3713 .handle = handle,
3714 .flags = .{ .nonblocking = false },
3715 };
3700 },3716 },
3701 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),3717 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3702 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),3718 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
...@@ -4271,7 +4287,10 @@ pub fn dirOpenFileWtf16(...@@ -4271,7 +4287,10 @@ pub fn dirOpenFileWtf16(
4271 errdefer w.CloseHandle(handle);4287 errdefer w.CloseHandle(handle);
42724288
4273 const exclusive = switch (flags.lock) {4289 const exclusive = switch (flags.lock) {
4274 .none => return .{ .handle = handle },4290 .none => return .{
4291 .handle = handle,
4292 .flags = .{ .nonblocking = false },
4293 },
4275 .shared => false,4294 .shared => false,
4276 .exclusive => true,4295 .exclusive => true,
4277 };4296 };
...@@ -4294,7 +4313,10 @@ pub fn dirOpenFileWtf16(...@@ -4294,7 +4313,10 @@ pub fn dirOpenFileWtf16(
4294 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer4313 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
4295 else => |status| return syscall.unexpectedNtstatus(status),4314 else => |status| return syscall.unexpectedNtstatus(status),
4296 };4315 };
4297 return .{ .handle = handle };4316 return .{
4317 .handle = handle,
4318 .flags = .{ .nonblocking = false },
4319 };
4298}4320}
42994321
4300fn dirOpenFileWasi(4322fn dirOpenFileWasi(
...@@ -8222,46 +8244,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz...@@ -8222,46 +8244,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
8222}8244}
82238245
8224fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {8246fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
8225 const DWORD = windows.DWORD;
8226 var index: usize = 0;8247 var index: usize = 0;
8227 while (index < data.len and data[index].len == 0) index += 1;8248 while (index < data.len and data[index].len == 0) index += 1;
8228 if (index == data.len) return 0;8249 if (index == data.len) return 0;
8229 const buffer = data[index];8250 const buffer = data[index];
8230 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
82318251
8232 const syscall: Syscall = try .start();8252 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8233 while (true) {8253
8234 var n: DWORD = undefined;8254 read: {
8235 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {8255 const syscall: Syscall = try .start();
8236 syscall.finish();8256 while (true) {
8237 return n;8257 switch (windows.ntdll.NtReadFile(
8258 file.handle,
8259 null, // event
8260 noopApc, // apc callback
8261 null, // apc context
8262 &io_status_block,
8263 buffer.ptr,
8264 @min(std.math.maxInt(u32), buffer.len),
8265 null, // byte offset
8266 null, // key
8267 )) {
8268 .SUCCESS => break :read syscall.finish(),
8269 .PENDING => break,
8270 .CANCELLED => {
8271 try syscall.checkCancel();
8272 continue;
8273 },
8274 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // wrong value for flags.nonblocking
8275 else => |status| return syscall.unexpectedNtstatus(status),
8276 }
8238 }8277 }
8239 switch (windows.GetLastError()) {8278 try syscall.toApc();
8240 .IO_PENDING => |err| {8279 while (true) {
8241 syscall.finish();8280 switch (windows.ntdll.NtDelayExecution(1, null)) {
8242 return windows.errorBug(err);8281 .USER_APC => break syscall.finish(),
8243 },8282 .SUCCESS, .CANCELLED => {
8244 .OPERATION_ABORTED => {8283 try syscall.checkCancel();
8245 try syscall.checkCancel();8284 continue;
8246 continue;8285 },
8247 },8286 else => |status| return syscall.unexpectedNtstatus(status),
8248 .BROKEN_PIPE, .HANDLE_EOF => {8287 }
8249 syscall.finish();
8250 return 0;
8251 },
8252 .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
8253 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8254 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8255 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
8256 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing
8257 // a handle to a directory.
8258 .INVALID_FUNCTION => return syscall.fail(error.IsDir),
8259 else => |err| {
8260 syscall.finish();
8261 return windows.unexpectedError(err);
8262 },
8263 }8288 }
8264 }8289 }
8290
8291 switch (io_status_block.u.Status) {
8292 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {},
8293 .ACCESS_DENIED => return error.AccessDenied,
8294 else => |status| return windows.unexpectedStatus(status),
8295 }
8296 return io_status_block.Information;
8297}
8298
8299fn noopApc(
8300 apc_context: ?*anyopaque,
8301 io_status_block: *windows.IO_STATUS_BLOCK,
8302 unused: windows.ULONG,
8303) callconv(.winapi) void {
8304 _ = apc_context;
8305 _ = io_status_block;
8306 _ = unused;
8265}8307}
82668308
8267fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {8309fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
...@@ -14321,9 +14363,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -14321,9 +14363,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
14321 return .{14363 return .{
14322 .id = piProcInfo.hProcess,14364 .id = piProcInfo.hProcess,
14323 .thread_handle = piProcInfo.hThread,14365 .thread_handle = piProcInfo.hThread,
14324 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,14366 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14325 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,14367 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14326 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,14368 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14327 .request_resource_usage_statistics = options.request_resource_usage_statistics,14369 .request_resource_usage_statistics = options.request_resource_usage_statistics,
14328 };14370 };
14329}14371}
...@@ -15457,6 +15499,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -15457,6 +15499,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
15457 .pointer => @ptrFromInt(int),15499 .pointer => @ptrFromInt(int),
15458 else => return error.UnsupportedOperation,15500 else => return error.UnsupportedOperation,
15459 },15501 },
15502 .flags = if (is_windows) .{ .nonblocking = true } else .{},
15460 };15503 };
15461}15504}
1546215505
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
...@@ -591,7 +591,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(...@@ -591,7 +591,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(
591591
592pub extern "ntdll" fn NtDelayExecution(592pub extern "ntdll" fn NtDelayExecution(
593 Alertable: BOOLEAN,593 Alertable: BOOLEAN,
594 DelayInterval: *const LARGE_INTEGER,594 DelayInterval: ?*const LARGE_INTEGER,
595) callconv(.winapi) NTSTATUS;595) callconv(.winapi) NTSTATUS;
596596
597pub extern "ntdll" fn NtCancelIoFileEx(597pub extern "ntdll" fn NtCancelIoFileEx(