std: consolidate all instances of std.Io.Threaded into a singleton

It's better to avoid references to this global variable, but, in the
cases where it's needed, such as in std.debug.print and collecting stack
traces, better to share the same instance.
This commit is contained in:
Andrew Kelley
2025-12-19 20:15:50 -08:00
parent 50c585227e
commit 77d2ad8c92
20 changed files with 56 additions and 53 deletions
+5 -5
View File
@@ -17,7 +17,7 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
var fba_buffer: [8192]u8 = undefined;
var stdin_buffer: [4096]u8 = undefined;
var stdout_buffer: [4096]u8 = undefined;
var runner_threaded_io: Io.Threaded = .init_single_threaded;
const runner_threaded_io: Io = Io.Threaded.global_single_threaded.ioBasic();
/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
/// the test runner will communicate with the build runner via `std.zig.Server`.
@@ -74,8 +74,8 @@ pub fn main() void {
fn mainServer() !void {
@disableInstrumentation();
var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer);
var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io.io(), &stdout_buffer);
var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
var server = try std.zig.Server.init(.{
.in = &stdin_reader.interface,
.out = &stdout_writer.interface,
@@ -224,11 +224,11 @@ fn mainTerminal() void {
var skip_count: usize = 0;
var fail_count: usize = 0;
var fuzz_count: usize = 0;
const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io.io(), .{
const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io, .{
.root_name = "Test",
.estimated_total_items = test_fn_list.len,
});
const have_tty = Io.File.stderr().isTty(runner_threaded_io.io()) catch unreachable;
const have_tty = Io.File.stderr().isTty(runner_threaded_io) catch unreachable;
var leaks: usize = 0;
for (test_fn_list, 0..) |test_fn, i| {
+13
View File
@@ -626,6 +626,19 @@ pub const init_single_threaded: Threaded = .{
},
};
var global_single_threaded_instance: Threaded = .init_single_threaded;
/// In general, the application is responsible for choosing the `Io`
/// implementation and library code should accept an `Io` parameter rather than
/// accessing this declaration. Most code should avoid referencing this
/// declaration entirely.
///
/// However, in some cases such as debugging, it is desirable to hardcode a
/// reference to this `Io` implementation.
///
/// This instance does not support concurrency or cancelation.
pub const global_single_threaded: *Threaded = &global_single_threaded_instance;
pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
t.mutex.lock();
defer t.mutex.unlock();
+1 -2
View File
@@ -322,8 +322,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
var buf: [32]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.ioBasic();
const io = Io.Threaded.global_single_threaded.ioBasic();
const file = try Io.Dir.cwd().openFile(io, path, .{});
defer file.close(io);
+6 -6
View File
@@ -263,7 +263,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
/// This is used for debug information and debug printing. It is intentionally
/// separate from the application's `Io` instance.
var static_single_threaded_io: Io.Threaded = .init_single_threaded;
const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic();
/// Allows the caller to freely write to stderr until `unlockStderr` is called.
///
@@ -284,7 +284,7 @@ var static_single_threaded_io: Io.Threaded = .init_single_threaded;
/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
/// application's chosen `Io` implementation.
pub fn lockStderr(buffer: []u8) Io.LockedStderr {
return static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) {
return static_single_threaded_io.lockStderr(buffer, null) catch |err| switch (err) {
// Impossible to cancel because no calls to cancel using
// `static_single_threaded_io` exist.
error.Canceled => unreachable,
@@ -292,7 +292,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr {
}
pub fn unlockStderr() void {
static_single_threaded_io.ioBasic().unlockStderr();
static_single_threaded_io.unlockStderr();
}
/// Writes to stderr, ignoring errors.
@@ -630,7 +630,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
defer it.deinit();
if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
const io = static_single_threaded_io.ioBasic();
const io = static_single_threaded_io;
var total_frames: usize = 0;
var index: usize = 0;
@@ -692,7 +692,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin
var total_frames: usize = 0;
var wait_for = options.first_address;
var printed_any_frame = false;
const io = static_single_threaded_io.ioBasic();
const io = static_single_threaded_io;
while (true) switch (it.next(io)) {
.switch_to_fp => |unwind_error| {
switch (StackIterator.fp_usability) {
@@ -800,7 +800,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void
return;
},
};
const io = static_single_threaded_io.ioBasic();
const io = static_single_threaded_io;
const captured_frames = @min(n_frames, st.instruction_addresses.len);
for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
// `ret_addr` is the return address, which is *after* the function call.
+2 -2
View File
@@ -315,8 +315,8 @@ const Module = struct {
);
if (len == 0) return error.MissingDebugInfo;
const name_w = name_buffer[0 .. len + 4 :0];
var threaded: Io.Threaded = .init_single_threaded;
const coff_file = threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
// TODO eliminate the reference to Io.Threaded.global_single_threaded here
const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
error.Canceled => |e| return e,
error.Unexpected => |e| return e,
error.FileNotFound => return error.MissingDebugInfo,
+1 -3
View File
@@ -142,8 +142,6 @@ const ElfDynLibError = error{
Streaming,
} || posix.OpenError || posix.MMapError;
var static_single_threaded_io: Io.Threaded = .init_single_threaded;
pub const ElfDynLib = struct {
strings: [*:0]u8,
syms: [*]elf.Sym,
@@ -224,7 +222,7 @@ pub const ElfDynLib = struct {
/// Trusts the file. Malicious file will be able to execute arbitrary code.
pub fn open(path: []const u8) Error!ElfDynLib {
const io = static_single_threaded_io.ioBasic();
const io = Io.Threaded.global_single_threaded.ioBasic();
const fd = try resolveFromName(io, path);
defer posix.close(fd);
+7 -8
View File
@@ -266,7 +266,7 @@ pub fn spawn(self: *Child, io: Io) SpawnError!void {
}
if (native_os == .windows) {
return self.spawnWindows();
return self.spawnWindows(io);
} else {
return self.spawnPosix(io);
}
@@ -750,7 +750,7 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void {
self.progress_node.setIpcFd(prog_pipe[0]);
}
fn spawnWindows(self: *Child) SpawnError!void {
fn spawnWindows(self: *Child, io: Io) SpawnError!void {
var saAttr = windows.SECURITY_ATTRIBUTES{
.nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
.bInheritHandle = windows.TRUE,
@@ -953,7 +953,7 @@ fn spawnWindows(self: *Child) SpawnError!void {
try dir_buf.appendSlice(self.allocator, app_dir);
}
windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
const original_err = switch (no_path_err) {
// argv[0] contains unsupported characters that will never resolve to a valid exe.
error.InvalidArg0 => return error.FileNotFound,
@@ -977,7 +977,7 @@ fn spawnWindows(self: *Child) SpawnError!void {
dir_buf.clearRetainingCapacity();
try dir_buf.appendSlice(self.allocator, search_path);
if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
if (windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
break :run;
} else |err| switch (err) {
// argv[0] contains unsupported characters that will never resolve to a valid exe.
@@ -1079,6 +1079,7 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
fn windowsCreateProcessPathExt(
allocator: Allocator,
io: Io,
dir_buf: *ArrayList(u16),
app_buf: *ArrayList(u16),
pathext: [:0]const u16,
@@ -1122,16 +1123,14 @@ fn windowsCreateProcessPathExt(
// Under those conditions, here we will have access to lower level directory
// opening function knowing which implementation we are in. Here, we imitate
// that scenario.
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.ioBasic();
var dir = dir: {
// needs to be null-terminated
try dir_buf.append(allocator, 0);
defer dir_buf.shrinkRetainingCapacity(dir_path_len);
const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
break :dir threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
// TODO eliminate this reference
break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
.iterate = true,
}) catch return error.FileNotFound;
};
@@ -8,7 +8,7 @@
const std = @import("std");
var some_enum: enum { first, second } = .first;
pub fn main() !void {
try std.Io.File.stdout().writeAll(@tagName(some_enum));
try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum));
}
#expect_stdout="first"
#update=no change
@@ -16,6 +16,6 @@ pub fn main() !void {
const std = @import("std");
var some_enum: enum { first, second } = .first;
pub fn main() !void {
try std.Io.File.stdout().writeAll(@tagName(some_enum));
try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum));
}
#expect_stdout="first"
+1 -2
View File
@@ -9,8 +9,7 @@ pub fn main() !void {
const actual_path = args[1];
const expected_path = args[2];
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024));
const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024));
+1 -2
View File
@@ -34,8 +34,7 @@ fn run(allocator: std.mem.Allocator) !void {
return error.BadUsage;
};
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
defer dir.close(io);
+1 -2
View File
@@ -29,8 +29,7 @@ fn run(allocator: std.mem.Allocator) !void {
const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable;
const basename = std.Io.Dir.path.basename(path);
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
defer dir.close(io);
+1 -2
View File
@@ -6,8 +6,7 @@ pub fn main() !void {
const args = try std.process.argsAlloc(arena);
if (args.len != 3) return error.BadUsage; // usage: 'check_differ <path a> <path b>'
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
@@ -11,8 +11,7 @@ pub fn main() !void {
var arg_it = try std.process.argsWithAllocator(arena);
_ = arg_it.next();
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
const cwd = std.Io.Dir.cwd();
const cwd_realpath = try cwd.realPathAlloc(io, arena, ".");
+1 -2
View File
@@ -14,8 +14,7 @@ pub fn main() !void {
const gpa = debug_allocator.allocator();
defer std.debug.assert(debug_allocator.deinit() == .ok);
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
// TODO this API isn't supposed to be used outside of unit testing. make it compilation error if used
// outside of unit testing.
@@ -8,8 +8,7 @@ pub fn main() !void {
if (args.len != 2) return error.BadUsage;
const path = args[1];
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
std.Io.Dir.cwd().access(io, path, .{}) catch return error.AccessFailed;
}
+1 -2
View File
@@ -1,8 +1,7 @@
const std = @import("std");
pub fn main() !void {
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
var args = try std.process.argsWithAllocator(std.heap.page_allocator);
_ = args.skip();
const filename = args.next().?;
@@ -1,8 +1,7 @@
const std = @import("std");
pub fn main() !void {
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
var args = try std.process.argsWithAllocator(std.heap.page_allocator);
_ = args.skip();
const dir_name = args.next().?;
@@ -15,8 +15,7 @@ pub fn main() anyerror!void {
const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path);
defer allocator.free(exe_rel_path);
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{});
}
+8 -3
View File
@@ -1,8 +1,13 @@
const std = @import("std");
var static_single_threaded_io: std.Io.Threaded = .init_single_threaded;
const io = static_single_threaded_io.ioBasic();
pub fn main() !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
const gpa = debug_allocator.allocator();
var threaded: std.Io.Threaded = .init(gpa);
defer threaded.deinit();
const io = threaded.io();
try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
}
+1 -2
View File
@@ -10,8 +10,7 @@ pub fn main() anyerror!void {
if (args.len < 2) return error.MissingArgs;
var threaded: Io.Threaded = .init_single_threaded;
const io = threaded.io();
const io = std.Io.Threaded.global_single_threaded.ioBasic();
const exe_path = args[1];