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.
This commit is contained in:
Andrew Kelley
2026-01-30 17:59:48 -08:00
parent dd463caa15
commit a9778e2549
5 changed files with 79 additions and 24 deletions
+18
View File
@@ -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 },
},
};
}
+52 -19
View File
@@ -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 },
};
}
+2 -2
View File
@@ -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 {
+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 = .{ .nonblocking = true },
};
const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
var bytes_read: usize = 0;
+6 -3
View File
@@ -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);