commit 39a6d5d1c5db32e9648fba6a46f3fef4ef83974a (tree)
parent 62c97b745d508a5ed7011bf3b8400fde4677b839
Author: Andrew Kelley <andrew@ziglang.org>
Date: Fri, 30 Jan 2026 17:59:48 -0800
std.Io.File: add non-blocking flag
On Windows, we need to know ahead of time whether a file was opened in
synchronous mode or asynchronous mode. There may be advantages to
tracking this state for POSIX operating systems as well.
Diffstat:
5 files changed, 79 insertions(+), 24 deletions(-)
diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig
@@ -10,6 +10,18 @@ const assert = std.debug.assert;
const Dir = std.Io.Dir;
handle: Handle,
+flags: Flags,
+
+pub const Flags = struct {
+ /// * true:
+ /// - windows: opened with MODE.IO.ASYNCHRONOUS
+ /// - POSIX: O_NONBLOCK is set
+ /// * false:
+ /// - windows: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or
+ /// not a file.
+ /// - POSIX: O_NONBLOCK is unset
+ nonblocking: bool,
+};
pub const Handle = std.posix.fd_t;
@@ -80,9 +92,11 @@ 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,
+ .flags = .{ .nonblocking = false },
},
};
}
@@ -91,9 +105,11 @@ 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,
+ .flags = .{ .nonblocking = false },
},
};
}
@@ -102,9 +118,11 @@ 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,
+ .flags = .{ .nonblocking = false },
},
};
}
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig
@@ -3215,8 +3215,10 @@ fn dirCreateDirPathOpenWasi(
fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
const t: *Threaded = @ptrCast(@alignCast(userdata));
- const file: File = .{ .handle = dir.handle };
- return fileStat(t, file);
+ return fileStat(t, .{
+ .handle = dir.handle,
+ .flags = .{ .nonblocking = false },
+ });
}
const dirStatFile = switch (native_os) {
@@ -4008,7 +4010,10 @@ fn dirCreateFilePosix(
}
}
- return .{ .handle = fd };
+ return .{
+ .handle = fd,
+ .flags = .{ .nonblocking = false },
+ };
}
fn dirCreateFileWindows(
@@ -4138,7 +4143,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,
};
@@ -4158,7 +4166,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),
@@ -4207,7 +4218,10 @@ fn dirCreateFileWasi(
switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
.SUCCESS => {
syscall.finish();
- return .{ .handle = fd };
+ return .{
+ .handle = fd,
+ .flags = .{ .nonblocking = false },
+ };
},
.INTR => {
try syscall.checkCancel();
@@ -4302,7 +4316,10 @@ fn dirCreateFileAtomic(
.SUCCESS => {
syscall.finish();
return .{
- .file = .{ .handle = @intCast(rc) },
+ .file = .{
+ .handle = @intCast(rc),
+ .flags = .{ .nonblocking = false },
+ },
.file_basename_hex = 0,
.dest_sub_path = dest_path,
.file_open = true,
@@ -4510,7 +4527,10 @@ fn dirOpenFilePosix(
if (!flags.allow_directory) {
const is_dir = is_dir: {
- const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {
+ const stat = fileStat(t, .{
+ .handle = fd,
+ .flags = .{ .nonblocking = false },
+ }) catch |err| switch (err) {
// The directory-ness is either unknown or unknowable
error.Streaming => break :is_dir false,
else => |e| return e,
@@ -4596,7 +4616,10 @@ fn dirOpenFilePosix(
}
}
- return .{ .handle = fd };
+ return .{
+ .handle = fd,
+ .flags = .{ .nonblocking = false },
+ };
}
fn dirOpenFileWindows(
@@ -4729,7 +4752,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,
};
@@ -4752,7 +4778,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(
@@ -4834,7 +4863,7 @@ fn dirOpenFileWasi(
if (!flags.allow_directory) {
const is_dir = is_dir: {
- const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {
+ const stat = fileStat(t, .{ .handle = fd, .flags = .{ .nonblocking = false } }) catch |err| switch (err) {
// The directory-ness is either unknown or unknowable
error.Streaming => break :is_dir false,
else => |e| return e,
@@ -4844,7 +4873,10 @@ fn dirOpenFileWasi(
if (is_dir) return error.IsDir;
}
- return .{ .handle = fd };
+ return .{
+ .handle = fd,
+ .flags = .{ .nonblocking = false },
+ };
}
const dirOpenDir = switch (native_os) {
@@ -14390,15 +14422,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
.pid = pid,
.err_fd = err_pipe[0],
.stdin = switch (options.stdin) {
- .pipe => .{ .handle = stdin_pipe[1] },
+ .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } },
else => null,
},
.stdout = switch (options.stdout) {
- .pipe => .{ .handle = stdout_pipe[0] },
+ .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } },
else => null,
},
.stderr = switch (options.stderr) {
- .pipe => .{ .handle = stderr_pipe[0] },
+ .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } },
else => null,
},
};
@@ -15052,9 +15084,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 = false } } 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,
};
}
@@ -16188,6 +16220,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
.pointer => @ptrFromInt(int),
else => return error.UnsupportedOperation,
},
+ .flags = .{ .nonblocking = false },
};
}
diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig
@@ -188,8 +188,8 @@ test "cancel blocked read from pipe" {
}),
else => {
const pipe = try std.Io.Threaded.pipe2(.{});
- read_end = .{ .handle = pipe[0] };
- write_end = .{ .handle = pipe[1] };
+ read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
+ write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
},
}
defer {
diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig
@@ -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 = .{ .nonblocking = true },
};
const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
var bytes_read: usize = 0;
diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig
@@ -126,8 +126,8 @@ test "pipe" {
const io = testing.io;
const fds = try std.Io.Threaded.pipe2(.{});
- const out: Io.File = .{ .handle = fds[0] };
- const in: Io.File = .{ .handle = fds[1] };
+ const out: Io.File = .{ .handle = fds[0], .flags = .{ .nonblocking = false } };
+ const in: Io.File = .{ .handle = fds[1], .flags = .{ .nonblocking = false } };
try in.writeStreamingAll(io, "hello");
var buf: [16]u8 = undefined;
try expect((try out.readStreaming(io, &.{&buf})) == 5);
@@ -150,7 +150,10 @@ test "memfd_create" {
else => return error.SkipZigTest,
}
- const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) };
+ const file: Io.File = .{
+ .handle = try posix.memfd_create("test", 0),
+ .flags = .{ .nonblocking = false },
+ };
defer file.close(io);
try file.writePositionalAll(io, "test", 0);