authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 17:59:48-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 17:59:48-08:00
loga9778e25496a148839d5de1024213ca96f39c5f8
tree688c00acbcfe81ea4da433799a680219436d42fb
parentdd463caa15f2474880085ddff073d619d02a5610

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.

5 files changed, 79 insertions(+), 24 deletions(-)

lib/std/Io/File.zig+18
...@@ -10,6 +10,18 @@ const assert = std.debug.assert;...@@ -10,6 +10,18 @@ const assert = std.debug.assert;
10const Dir = std.Io.Dir;10const Dir = std.Io.Dir;
1111
12handle: Handle,12handle: Handle,
13flags: Flags,
14
15pub const Flags = struct {
16 /// * true:
17 /// - windows: opened with MODE.IO.ASYNCHRONOUS
18 /// - POSIX: O_NONBLOCK is set
19 /// * false:
20 /// - windows: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or
21 /// not a file.
22 /// - POSIX: O_NONBLOCK is unset
23 nonblocking: bool,
24};
1325
14pub const Handle = std.posix.fd_t;26pub const Handle = std.posix.fd_t;
1527
...@@ -80,9 +92,11 @@ pub fn stdout() File {...@@ -80,9 +92,11 @@ pub fn stdout() File {
80 return switch (native_os) {92 return switch (native_os) {
81 .windows => .{93 .windows => .{
82 .handle = std.os.windows.peb().ProcessParameters.hStdOutput,94 .handle = std.os.windows.peb().ProcessParameters.hStdOutput,
95 .flags = .{ .nonblocking = false },
83 },96 },
84 else => .{97 else => .{
85 .handle = std.posix.STDOUT_FILENO,98 .handle = std.posix.STDOUT_FILENO,
99 .flags = .{ .nonblocking = false },
86 },100 },
87 };101 };
88}102}
...@@ -91,9 +105,11 @@ pub fn stderr() File {...@@ -91,9 +105,11 @@ pub fn stderr() File {
91 return switch (native_os) {105 return switch (native_os) {
92 .windows => .{106 .windows => .{
93 .handle = std.os.windows.peb().ProcessParameters.hStdError,107 .handle = std.os.windows.peb().ProcessParameters.hStdError,
108 .flags = .{ .nonblocking = false },
94 },109 },
95 else => .{110 else => .{
96 .handle = std.posix.STDERR_FILENO,111 .handle = std.posix.STDERR_FILENO,
112 .flags = .{ .nonblocking = false },
97 },113 },
98 };114 };
99}115}
...@@ -102,9 +118,11 @@ pub fn stdin() File {...@@ -102,9 +118,11 @@ pub fn stdin() File {
102 return switch (native_os) {118 return switch (native_os) {
103 .windows => .{119 .windows => .{
104 .handle = std.os.windows.peb().ProcessParameters.hStdInput,120 .handle = std.os.windows.peb().ProcessParameters.hStdInput,
121 .flags = .{ .nonblocking = false },
105 },122 },
106 else => .{123 else => .{
107 .handle = std.posix.STDIN_FILENO,124 .handle = std.posix.STDIN_FILENO,
125 .flags = .{ .nonblocking = false },
108 },126 },
109 };127 };
110}128}
lib/std/Io/Threaded.zig+52-19
...@@ -3215,8 +3215,10 @@ fn dirCreateDirPathOpenWasi(...@@ -3215,8 +3215,10 @@ fn dirCreateDirPathOpenWasi(
32153215
3216fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {3216fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
3217 const t: *Threaded = @ptrCast(@alignCast(userdata));3217 const t: *Threaded = @ptrCast(@alignCast(userdata));
3218 const file: File = .{ .handle = dir.handle };3218 return fileStat(t, .{
3219 return fileStat(t, file);3219 .handle = dir.handle,
3220 .flags = .{ .nonblocking = false },
3221 });
3220}3222}
32213223
3222const dirStatFile = switch (native_os) {3224const dirStatFile = switch (native_os) {
...@@ -4008,7 +4010,10 @@ fn dirCreateFilePosix(...@@ -4008,7 +4010,10 @@ fn dirCreateFilePosix(
4008 }4010 }
4009 }4011 }
40104012
4011 return .{ .handle = fd };4013 return .{
4014 .handle = fd,
4015 .flags = .{ .nonblocking = false },
4016 };
4012}4017}
40134018
4014fn dirCreateFileWindows(4019fn dirCreateFileWindows(
...@@ -4138,7 +4143,10 @@ fn dirCreateFileWindows(...@@ -4138,7 +4143,10 @@ fn dirCreateFileWindows(
4138 errdefer windows.CloseHandle(handle);4143 errdefer windows.CloseHandle(handle);
41394144
4140 const exclusive = switch (flags.lock) {4145 const exclusive = switch (flags.lock) {
4141 .none => return .{ .handle = handle },4146 .none => return .{
4147 .handle = handle,
4148 .flags = .{ .nonblocking = false },
4149 },
4142 .shared => false,4150 .shared => false,
4143 .exclusive => true,4151 .exclusive => true,
4144 };4152 };
...@@ -4158,7 +4166,10 @@ fn dirCreateFileWindows(...@@ -4158,7 +4166,10 @@ fn dirCreateFileWindows(
4158 )) {4166 )) {
4159 .SUCCESS => {4167 .SUCCESS => {
4160 syscall.finish();4168 syscall.finish();
4161 return .{ .handle = handle };4169 return .{
4170 .handle = handle,
4171 .flags = .{ .nonblocking = false },
4172 };
4162 },4173 },
4163 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),4174 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
4164 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),4175 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
...@@ -4207,7 +4218,10 @@ fn dirCreateFileWasi(...@@ -4207,7 +4218,10 @@ fn dirCreateFileWasi(
4207 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {4218 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
4208 .SUCCESS => {4219 .SUCCESS => {
4209 syscall.finish();4220 syscall.finish();
4210 return .{ .handle = fd };4221 return .{
4222 .handle = fd,
4223 .flags = .{ .nonblocking = false },
4224 };
4211 },4225 },
4212 .INTR => {4226 .INTR => {
4213 try syscall.checkCancel();4227 try syscall.checkCancel();
...@@ -4302,7 +4316,10 @@ fn dirCreateFileAtomic(...@@ -4302,7 +4316,10 @@ fn dirCreateFileAtomic(
4302 .SUCCESS => {4316 .SUCCESS => {
4303 syscall.finish();4317 syscall.finish();
4304 return .{4318 return .{
4305 .file = .{ .handle = @intCast(rc) },4319 .file = .{
4320 .handle = @intCast(rc),
4321 .flags = .{ .nonblocking = false },
4322 },
4306 .file_basename_hex = 0,4323 .file_basename_hex = 0,
4307 .dest_sub_path = dest_path,4324 .dest_sub_path = dest_path,
4308 .file_open = true,4325 .file_open = true,
...@@ -4510,7 +4527,10 @@ fn dirOpenFilePosix(...@@ -4510,7 +4527,10 @@ fn dirOpenFilePosix(
45104527
4511 if (!flags.allow_directory) {4528 if (!flags.allow_directory) {
4512 const is_dir = is_dir: {4529 const is_dir = is_dir: {
4513 const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {4530 const stat = fileStat(t, .{
4531 .handle = fd,
4532 .flags = .{ .nonblocking = false },
4533 }) catch |err| switch (err) {
4514 // The directory-ness is either unknown or unknowable4534 // The directory-ness is either unknown or unknowable
4515 error.Streaming => break :is_dir false,4535 error.Streaming => break :is_dir false,
4516 else => |e| return e,4536 else => |e| return e,
...@@ -4596,7 +4616,10 @@ fn dirOpenFilePosix(...@@ -4596,7 +4616,10 @@ fn dirOpenFilePosix(
4596 }4616 }
4597 }4617 }
45984618
4599 return .{ .handle = fd };4619 return .{
4620 .handle = fd,
4621 .flags = .{ .nonblocking = false },
4622 };
4600}4623}
46014624
4602fn dirOpenFileWindows(4625fn dirOpenFileWindows(
...@@ -4729,7 +4752,10 @@ pub fn dirOpenFileWtf16(...@@ -4729,7 +4752,10 @@ pub fn dirOpenFileWtf16(
4729 errdefer w.CloseHandle(handle);4752 errdefer w.CloseHandle(handle);
47304753
4731 const exclusive = switch (flags.lock) {4754 const exclusive = switch (flags.lock) {
4732 .none => return .{ .handle = handle },4755 .none => return .{
4756 .handle = handle,
4757 .flags = .{ .nonblocking = false },
4758 },
4733 .shared => false,4759 .shared => false,
4734 .exclusive => true,4760 .exclusive => true,
4735 };4761 };
...@@ -4752,7 +4778,10 @@ pub fn dirOpenFileWtf16(...@@ -4752,7 +4778,10 @@ pub fn dirOpenFileWtf16(
4752 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer4778 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
4753 else => |status| return syscall.unexpectedNtstatus(status),4779 else => |status| return syscall.unexpectedNtstatus(status),
4754 };4780 };
4755 return .{ .handle = handle };4781 return .{
4782 .handle = handle,
4783 .flags = .{ .nonblocking = false },
4784 };
4756}4785}
47574786
4758fn dirOpenFileWasi(4787fn dirOpenFileWasi(
...@@ -4834,7 +4863,7 @@ fn dirOpenFileWasi(...@@ -4834,7 +4863,7 @@ fn dirOpenFileWasi(
48344863
4835 if (!flags.allow_directory) {4864 if (!flags.allow_directory) {
4836 const is_dir = is_dir: {4865 const is_dir = is_dir: {
4837 const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {4866 const stat = fileStat(t, .{ .handle = fd, .flags = .{ .nonblocking = false } }) catch |err| switch (err) {
4838 // The directory-ness is either unknown or unknowable4867 // The directory-ness is either unknown or unknowable
4839 error.Streaming => break :is_dir false,4868 error.Streaming => break :is_dir false,
4840 else => |e| return e,4869 else => |e| return e,
...@@ -4844,7 +4873,10 @@ fn dirOpenFileWasi(...@@ -4844,7 +4873,10 @@ fn dirOpenFileWasi(
4844 if (is_dir) return error.IsDir;4873 if (is_dir) return error.IsDir;
4845 }4874 }
48464875
4847 return .{ .handle = fd };4876 return .{
4877 .handle = fd,
4878 .flags = .{ .nonblocking = false },
4879 };
4848}4880}
48494881
4850const dirOpenDir = switch (native_os) {4882const dirOpenDir = switch (native_os) {
...@@ -14390,15 +14422,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -14390,15 +14422,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
14390 .pid = pid,14422 .pid = pid,
14391 .err_fd = err_pipe[0],14423 .err_fd = err_pipe[0],
14392 .stdin = switch (options.stdin) {14424 .stdin = switch (options.stdin) {
14393 .pipe => .{ .handle = stdin_pipe[1] },14425 .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } },
14394 else => null,14426 else => null,
14395 },14427 },
14396 .stdout = switch (options.stdout) {14428 .stdout = switch (options.stdout) {
14397 .pipe => .{ .handle = stdout_pipe[0] },14429 .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } },
14398 else => null,14430 else => null,
14399 },14431 },
14400 .stderr = switch (options.stderr) {14432 .stderr = switch (options.stderr) {
14401 .pipe => .{ .handle = stderr_pipe[0] },14433 .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } },
14402 else => null,14434 else => null,
14403 },14435 },
14404 };14436 };
...@@ -15052,9 +15084,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -15052,9 +15084,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
15052 return .{15084 return .{
15053 .id = piProcInfo.hProcess,15085 .id = piProcInfo.hProcess,
15054 .thread_handle = piProcInfo.hThread,15086 .thread_handle = piProcInfo.hThread,
15055 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,15087 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,
15056 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,15088 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15057 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,15089 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
15058 .request_resource_usage_statistics = options.request_resource_usage_statistics,15090 .request_resource_usage_statistics = options.request_resource_usage_statistics,
15059 };15091 };
15060}15092}
...@@ -16188,6 +16220,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -16188,6 +16220,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
16188 .pointer => @ptrFromInt(int),16220 .pointer => @ptrFromInt(int),
16189 else => return error.UnsupportedOperation,16221 else => return error.UnsupportedOperation,
16190 },16222 },
16223 .flags = .{ .nonblocking = false },
16191 };16224 };
16192}16225}
1619316226
lib/std/Io/Threaded/test.zig+2-2
...@@ -188,8 +188,8 @@ test "cancel blocked read from pipe" {...@@ -188,8 +188,8 @@ test "cancel blocked read from pipe" {
188 }),188 }),
189 else => {189 else => {
190 const pipe = try std.Io.Threaded.pipe2(.{});190 const pipe = try std.Io.Threaded.pipe2(.{});
191 read_end = .{ .handle = pipe[0] };191 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
192 write_end = .{ .handle = pipe[1] };192 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
193 },193 },
194 }194 }
195 defer {195 defer {
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 = .{ .nonblocking = true },
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/posix/test.zig+6-3
...@@ -126,8 +126,8 @@ test "pipe" {...@@ -126,8 +126,8 @@ test "pipe" {
126 const io = testing.io;126 const io = testing.io;
127127
128 const fds = try std.Io.Threaded.pipe2(.{});128 const fds = try std.Io.Threaded.pipe2(.{});
129 const out: Io.File = .{ .handle = fds[0] };129 const out: Io.File = .{ .handle = fds[0], .flags = .{ .nonblocking = false } };
130 const in: Io.File = .{ .handle = fds[1] };130 const in: Io.File = .{ .handle = fds[1], .flags = .{ .nonblocking = false } };
131 try in.writeStreamingAll(io, "hello");131 try in.writeStreamingAll(io, "hello");
132 var buf: [16]u8 = undefined;132 var buf: [16]u8 = undefined;
133 try expect((try out.readStreaming(io, &.{&buf})) == 5);133 try expect((try out.readStreaming(io, &.{&buf})) == 5);
...@@ -150,7 +150,10 @@ test "memfd_create" {...@@ -150,7 +150,10 @@ test "memfd_create" {
150 else => return error.SkipZigTest,150 else => return error.SkipZigTest,
151 }151 }
152152
153 const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) };153 const file: Io.File = .{
154 .handle = try posix.memfd_create("test", 0),
155 .flags = .{ .nonblocking = false },
156 };
154 defer file.close(io);157 defer file.close(io);
155 try file.writePositionalAll(io, "test", 0);158 try file.writePositionalAll(io, "test", 0);
156159