mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 18:51:44 +03:00
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:
@@ -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
@@ -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 .{},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user