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.
This commit is contained in:
Andrew Kelley
2026-01-20 17:05:14 -08:00
parent 0f51f663f0
commit de3a2c0ebb
4 changed files with 100 additions and 41 deletions
+15
View File
@@ -10,8 +10,20 @@ const assert = std.debug.assert;
const Dir = std.Io.Dir;
handle: Handle,
flags: Flags = .{},
pub const Handle = std.posix.fd_t;
pub const Flags = switch (native_os) {
.windows => packed struct(u1) {
/// * true: opened with MODE.IO.ASYNCHRONOUS
/// * false: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or
/// not a file.
/// This is default-initialized to false as a workaround for
/// https://codeberg.org/ziglang/zig/issues/30842
nonblocking: bool = false,
},
else => packed struct(u0) {},
};
pub const Reader = @import("File/Reader.zig");
pub const Writer = @import("File/Writer.zig");
@@ -77,6 +89,7 @@ pub fn stdout() File {
return switch (native_os) {
.windows => .{
.handle = std.os.windows.peb().ProcessParameters.hStdOutput,
.flags = .{ .nonblocking = false },
},
else => .{
.handle = std.posix.STDOUT_FILENO,
@@ -88,6 +101,7 @@ pub fn stderr() File {
return switch (native_os) {
.windows => .{
.handle = std.os.windows.peb().ProcessParameters.hStdError,
.flags = .{ .nonblocking = false },
},
else => .{
.handle = std.posix.STDERR_FILENO,
@@ -99,6 +113,7 @@ pub fn stdin() File {
return switch (native_os) {
.windows => .{
.handle = std.os.windows.peb().ProcessParameters.hStdInput,
.flags = .{ .nonblocking = false },
},
else => .{
.handle = std.posix.STDIN_FILENO,
+83 -40
View File
@@ -1173,6 +1173,11 @@ const Syscall = struct {
.blocked_canceling => return error.Canceled, // new status is `.canceled`
}
}
fn toApc(s: Syscall) Io.Cancelable!void {
// TODO set state to indicate instead of NtCancelSynchronousIoFile we
// need to use NtCancelIoFileEx
return s.checkCancel();
}
/// Marks this syscall as finished.
fn finish(s: Syscall) void {
const thread = s.thread orelse return;
@@ -2754,7 +2759,12 @@ fn dirCreateDirPathOpenWasi(
fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
const file: File = .{ .handle = dir.handle };
const file: File = if (is_windows) .{
.handle = dir.handle,
.flags = .{ .nonblocking = false },
} else .{
.handle = dir.handle,
};
return fileStat(t, file);
}
@@ -3676,7 +3686,10 @@ fn dirCreateFileWindows(
errdefer windows.CloseHandle(handle);
const exclusive = switch (flags.lock) {
.none => return .{ .handle = handle },
.none => return .{
.handle = handle,
.flags = .{ .nonblocking = false },
},
.shared => false,
.exclusive => true,
};
@@ -3696,7 +3709,10 @@ fn dirCreateFileWindows(
)) {
.SUCCESS => {
syscall.finish();
return .{ .handle = handle };
return .{
.handle = handle,
.flags = .{ .nonblocking = false },
};
},
.INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
.LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
@@ -4271,7 +4287,10 @@ pub fn dirOpenFileWtf16(
errdefer w.CloseHandle(handle);
const exclusive = switch (flags.lock) {
.none => return .{ .handle = handle },
.none => return .{
.handle = handle,
.flags = .{ .nonblocking = false },
},
.shared => false,
.exclusive => true,
};
@@ -4294,7 +4313,10 @@ pub fn dirOpenFileWtf16(
.ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
else => |status| return syscall.unexpectedNtstatus(status),
};
return .{ .handle = handle };
return .{
.handle = handle,
.flags = .{ .nonblocking = false },
};
}
fn dirOpenFileWasi(
@@ -8222,46 +8244,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
}
fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
const DWORD = windows.DWORD;
var index: usize = 0;
while (index < data.len and data[index].len == 0) index += 1;
if (index == data.len) return 0;
const buffer = data[index];
const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);
const syscall: Syscall = try .start();
while (true) {
var n: DWORD = undefined;
if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {
syscall.finish();
return n;
var io_status_block: windows.IO_STATUS_BLOCK = undefined;
read: {
const syscall: Syscall = try .start();
while (true) {
switch (windows.ntdll.NtReadFile(
file.handle,
null, // event
noopApc, // apc callback
null, // apc context
&io_status_block,
buffer.ptr,
@min(std.math.maxInt(u32), buffer.len),
null, // byte offset
null, // key
)) {
.SUCCESS => break :read syscall.finish(),
.PENDING => break,
.CANCELLED => {
try syscall.checkCancel();
continue;
},
.INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // wrong value for flags.nonblocking
else => |status| return syscall.unexpectedNtstatus(status),
}
}
switch (windows.GetLastError()) {
.IO_PENDING => |err| {
syscall.finish();
return windows.errorBug(err);
},
.OPERATION_ABORTED => {
try syscall.checkCancel();
continue;
},
.BROKEN_PIPE, .HANDLE_EOF => {
syscall.finish();
return 0;
},
.NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
.LOCK_VIOLATION => return syscall.fail(error.LockViolation),
.ACCESS_DENIED => return syscall.fail(error.AccessDenied),
.INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
// TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing
// a handle to a directory.
.INVALID_FUNCTION => return syscall.fail(error.IsDir),
else => |err| {
syscall.finish();
return windows.unexpectedError(err);
},
try syscall.toApc();
while (true) {
switch (windows.ntdll.NtDelayExecution(1, null)) {
.USER_APC => break syscall.finish(),
.SUCCESS, .CANCELLED => {
try syscall.checkCancel();
continue;
},
else => |status| return syscall.unexpectedNtstatus(status),
}
}
}
switch (io_status_block.u.Status) {
.SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {},
.ACCESS_DENIED => return error.AccessDenied,
else => |status| return windows.unexpectedStatus(status),
}
return io_status_block.Information;
}
fn noopApc(
apc_context: ?*anyopaque,
io_status_block: *windows.IO_STATUS_BLOCK,
unused: windows.ULONG,
) callconv(.winapi) void {
_ = apc_context;
_ = io_status_block;
_ = unused;
}
fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
@@ -14321,9 +14363,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
return .{
.id = piProcInfo.hProcess,
.thread_handle = piProcInfo.hThread,
.stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,
.stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,
.stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,
.stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
.stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
.stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
.request_resource_usage_statistics = options.request_resource_usage_statistics,
};
}
@@ -15457,6 +15499,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
.pointer => @ptrFromInt(int),
else => return error.UnsupportedOperation,
},
.flags = if (is_windows) .{ .nonblocking = true } else .{},
};
}
+1
View File
@@ -979,6 +979,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
if (main_parent == .unused) continue;
const file: Io.File = .{
.handle = main_storage.getIpcFd() orelse continue,
.flags = if (is_windows) .{ .nonblocking = true } else .{},
};
const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
var bytes_read: usize = 0;
+1 -1
View File
@@ -591,7 +591,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(
pub extern "ntdll" fn NtDelayExecution(
Alertable: BOOLEAN,
DelayInterval: *const LARGE_INTEGER,
DelayInterval: ?*const LARGE_INTEGER,
) callconv(.winapi) NTSTATUS;
pub extern "ntdll" fn NtCancelIoFileEx(