From 77d2ad8c929680ed35fcfe6646f940518a07e7e4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 19 Dec 2025 20:15:50 -0800 Subject: [PATCH] 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. --- lib/compiler/test_runner.zig | 10 +++++----- lib/std/Io/Threaded.zig | 13 +++++++++++++ lib/std/Thread.zig | 3 +-- lib/std/debug.zig | 12 ++++++------ lib/std/debug/SelfInfo/Windows.zig | 4 ++-- lib/std/dynamic_library.zig | 4 +--- lib/std/process/Child.zig | 15 +++++++-------- test/incremental/no_change_preserves_tag_names | 4 ++-- test/standalone/cmakedefine/check.zig | 3 +-- test/standalone/dirname/exists_in.zig | 3 +-- test/standalone/dirname/touch.zig | 3 +-- test/standalone/entry_point/check_differ.zig | 3 +-- test/standalone/install_headers/check_exists.zig | 3 +-- test/standalone/posix/relpaths.zig | 3 +-- test/standalone/run_cwd/check_file_exists.zig | 3 +-- test/standalone/run_output_caching/main.zig | 3 +-- test/standalone/run_output_paths/create_file.zig | 3 +-- .../self_exe_symlink/create-symlink.zig | 3 +-- test/standalone/simple/hello_world/hello.zig | 11 ++++++++--- test/standalone/windows_paths/test.zig | 3 +-- 20 files changed, 56 insertions(+), 53 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index d16fc3ae82..84664feb13 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -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| { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index db4c5f3fda..182a8f4b6d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -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(); diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index fbce1cd000..f25c664000 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -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); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 26631db501..0e804e2348 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -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. diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index c34c60f3ec..99d3e9f926 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -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, diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 18db4ad8c6..70b236f3e5 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -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); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 05cc4b3944..0db02aa824 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -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; }; diff --git a/test/incremental/no_change_preserves_tag_names b/test/incremental/no_change_preserves_tag_names index e399e083e1..6675d74166 100644 --- a/test/incremental/no_change_preserves_tag_names +++ b/test/incremental/no_change_preserves_tag_names @@ -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" diff --git a/test/standalone/cmakedefine/check.zig b/test/standalone/cmakedefine/check.zig index d0751913d7..c2f89ad112 100644 --- a/test/standalone/cmakedefine/check.zig +++ b/test/standalone/cmakedefine/check.zig @@ -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)); diff --git a/test/standalone/dirname/exists_in.zig b/test/standalone/dirname/exists_in.zig index 7bddc4e613..b321f46c55 100644 --- a/test/standalone/dirname/exists_in.zig +++ b/test/standalone/dirname/exists_in.zig @@ -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); diff --git a/test/standalone/dirname/touch.zig b/test/standalone/dirname/touch.zig index 9255d27c72..d7f9b71553 100644 --- a/test/standalone/dirname/touch.zig +++ b/test/standalone/dirname/touch.zig @@ -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); diff --git a/test/standalone/entry_point/check_differ.zig b/test/standalone/entry_point/check_differ.zig index bba45e5f8c..29b333632f 100644 --- a/test/standalone/entry_point/check_differ.zig +++ b/test/standalone/entry_point/check_differ.zig @@ -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 ' - 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 diff --git a/test/standalone/install_headers/check_exists.zig b/test/standalone/install_headers/check_exists.zig index 8a7104e4f2..22638cf167 100644 --- a/test/standalone/install_headers/check_exists.zig +++ b/test/standalone/install_headers/check_exists.zig @@ -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, "."); diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index ed143ccdc2..447285c444 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -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. diff --git a/test/standalone/run_cwd/check_file_exists.zig b/test/standalone/run_cwd/check_file_exists.zig index 8830cc115a..a885c7dafd 100644 --- a/test/standalone/run_cwd/check_file_exists.zig +++ b/test/standalone/run_cwd/check_file_exists.zig @@ -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; } diff --git a/test/standalone/run_output_caching/main.zig b/test/standalone/run_output_caching/main.zig index c21d8d0f6b..66e64beda2 100644 --- a/test/standalone/run_output_caching/main.zig +++ b/test/standalone/run_output_caching/main.zig @@ -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().?; diff --git a/test/standalone/run_output_paths/create_file.zig b/test/standalone/run_output_paths/create_file.zig index 4830790c79..27f741ecab 100644 --- a/test/standalone/run_output_paths/create_file.zig +++ b/test/standalone/run_output_paths/create_file.zig @@ -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().?; diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index 6ccf6596aa..d725207320 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -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, .{}); } diff --git a/test/standalone/simple/hello_world/hello.zig b/test/standalone/simple/hello_world/hello.zig index d708394230..6bce841344 100644 --- a/test/standalone/simple/hello_world/hello.zig +++ b/test/standalone/simple/hello_world/hello.zig @@ -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"); } diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index f5fa594766..ed4069dc61 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -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];