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;
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;
......@@ -2754,7 +2759,12 @@ fn dirCreateDirPathOpenWasi(
27542759
27552760fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
27562761 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 };
27582768 return fileStat(t, file);
27592769}
27602770
......@@ -3676,7 +3686,10 @@ fn dirCreateFileWindows(
36763686 errdefer windows.CloseHandle(handle);
36773687
36783688 const exclusive = switch (flags.lock) {
3679 .none => return .{ .handle = handle },
3689 .none => return .{
3690 .handle = handle,
3691 .flags = .{ .nonblocking = false },
3692 },
36803693 .shared => false,
36813694 .exclusive => true,
36823695 };
......@@ -3696,7 +3709,10 @@ fn dirCreateFileWindows(
36963709 )) {
36973710 .SUCCESS => {
36983711 syscall.finish();
3699 return .{ .handle = handle };
3712 return .{
3713 .handle = handle,
3714 .flags = .{ .nonblocking = false },
3715 };
37003716 },
37013717 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
37023718 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
......@@ -4271,7 +4287,10 @@ pub fn dirOpenFileWtf16(
42714287 errdefer w.CloseHandle(handle);
42724288
42734289 const exclusive = switch (flags.lock) {
4274 .none => return .{ .handle = handle },
4290 .none => return .{
4291 .handle = handle,
4292 .flags = .{ .nonblocking = false },
4293 },
42754294 .shared => false,
42764295 .exclusive => true,
42774296 };
......@@ -4294,7 +4313,10 @@ pub fn dirOpenFileWtf16(
42944313 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
42954314 else => |status| return syscall.unexpectedNtstatus(status),
42964315 };
4297 return .{ .handle = handle };
4316 return .{
4317 .handle = handle,
4318 .flags = .{ .nonblocking = false },
4319 };
42984320}
42994321
43004322fn dirOpenFileWasi(
......@@ -8222,46 +8244,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
82228244}
82238245
82248246fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
8225 const DWORD = windows.DWORD;
82268247 var index: usize = 0;
82278248 while (index < data.len and data[index].len == 0) index += 1;
82288249 if (index == data.len) return 0;
82298250 const buffer = data[index];
8230 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
82318251
8232 const syscall: Syscall = try .start();
8233 while (true) {
8234 var n: DWORD = undefined;
8235 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {
8236 syscall.finish();
8237 return n;
8252 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8253
8254 read: {
8255 const syscall: Syscall = try .start();
8256 while (true) {
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 }
82388277 }
8239 switch (windows.GetLastError()) {
8240 .IO_PENDING => |err| {
8241 syscall.finish();
8242 return windows.errorBug(err);
8243 },
8244 .OPERATION_ABORTED => {
8245 try syscall.checkCancel();
8246 continue;
8247 },
8248 .BROKEN_PIPE, .HANDLE_EOF => {
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 },
8278 try syscall.toApc();
8279 while (true) {
8280 switch (windows.ntdll.NtDelayExecution(1, null)) {
8281 .USER_APC => break syscall.finish(),
8282 .SUCCESS, .CANCELLED => {
8283 try syscall.checkCancel();
8284 continue;
8285 },
8286 else => |status| return syscall.unexpectedNtstatus(status),
8287 }
82638288 }
82648289 }
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;
82658307}
82668308
82678309fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
......@@ -14321,9 +14363,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1432114363 return .{
1432214364 .id = piProcInfo.hProcess,
1432314365 .thread_handle = piProcInfo.hThread,
14324 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,
14325 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,
14326 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,
14366 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14367 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14368 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
1432714369 .request_resource_usage_statistics = options.request_resource_usage_statistics,
1432814370 };
1432914371}
......@@ -15457,6 +15499,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
1545715499 .pointer => @ptrFromInt(int),
1545815500 else => return error.UnsupportedOperation,
1545915501 },
15502 .flags = if (is_windows) .{ .nonblocking = true } else .{},
1546015503 };
1546115504}
1546215505
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
......@@ -591,7 +591,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(
591591
592592pub extern "ntdll" fn NtDelayExecution(
593593 Alertable: BOOLEAN,
594 DelayInterval: *const LARGE_INTEGER,
594 DelayInterval: ?*const LARGE_INTEGER,
595595) callconv(.winapi) NTSTATUS;
596596
597597pub extern "ntdll" fn NtCancelIoFileEx(