authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-19 20:15:50-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:11-08:00
log77d2ad8c929680ed35fcfe6646f940518a07e7e4
treef07cbefcc9b20049369188bfac27734a3c37c655
parent50c585227ed2a57a4c1cf3f3b44914881999559d

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.

20 files changed, 56 insertions(+), 53 deletions(-)

lib/compiler/test_runner.zig+5-5
......@@ -17,7 +17,7 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
1717var fba_buffer: [8192]u8 = undefined;
1818var stdin_buffer: [4096]u8 = undefined;
1919var stdout_buffer: [4096]u8 = undefined;
20var runner_threaded_io: Io.Threaded = .init_single_threaded;
20const runner_threaded_io: Io = Io.Threaded.global_single_threaded.ioBasic();
2121
2222/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
2323/// the test runner will communicate with the build runner via `std.zig.Server`.
......@@ -74,8 +74,8 @@ pub fn main() void {
7474
7575fn mainServer() !void {
7676 @disableInstrumentation();
77 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io.io(), &stdin_buffer);
78 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io.io(), &stdout_buffer);
77 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
78 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
7979 var server = try std.zig.Server.init(.{
8080 .in = &stdin_reader.interface,
8181 .out = &stdout_writer.interface,
......@@ -224,11 +224,11 @@ fn mainTerminal() void {
224224 var skip_count: usize = 0;
225225 var fail_count: usize = 0;
226226 var fuzz_count: usize = 0;
227 const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io.io(), .{
227 const root_node = if (builtin.fuzz) std.Progress.Node.none else std.Progress.start(runner_threaded_io, .{
228228 .root_name = "Test",
229229 .estimated_total_items = test_fn_list.len,
230230 });
231 const have_tty = Io.File.stderr().isTty(runner_threaded_io.io()) catch unreachable;
231 const have_tty = Io.File.stderr().isTty(runner_threaded_io) catch unreachable;
232232
233233 var leaks: usize = 0;
234234 for (test_fn_list, 0..) |test_fn, i| {
lib/std/Io/Threaded.zig+13
......@@ -626,6 +626,19 @@ pub const init_single_threaded: Threaded = .{
626626 },
627627};
628628
629var global_single_threaded_instance: Threaded = .init_single_threaded;
630
631/// In general, the application is responsible for choosing the `Io`
632/// implementation and library code should accept an `Io` parameter rather than
633/// accessing this declaration. Most code should avoid referencing this
634/// declaration entirely.
635///
636/// However, in some cases such as debugging, it is desirable to hardcode a
637/// reference to this `Io` implementation.
638///
639/// This instance does not support concurrency or cancelation.
640pub const global_single_threaded: *Threaded = &global_single_threaded_instance;
641
629642pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void {
630643 t.mutex.lock();
631644 defer t.mutex.unlock();
lib/std/Thread.zig+1-2
......@@ -322,8 +322,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
322322 var buf: [32]u8 = undefined;
323323 const path = try std.fmt.bufPrint(&buf, "/proc/self/task/{d}/comm", .{self.getHandle()});
324324
325 var threaded: std.Io.Threaded = .init_single_threaded;
326 const io = threaded.ioBasic();
325 const io = Io.Threaded.global_single_threaded.ioBasic();
327326
328327 const file = try Io.Dir.cwd().openFile(io, path, .{});
329328 defer file.close(io);
lib/std/debug.zig+6-6
......@@ -263,7 +263,7 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
263263
264264/// This is used for debug information and debug printing. It is intentionally
265265/// separate from the application's `Io` instance.
266var static_single_threaded_io: Io.Threaded = .init_single_threaded;
266const static_single_threaded_io = Io.Threaded.global_single_threaded.ioBasic();
267267
268268/// Allows the caller to freely write to stderr until `unlockStderr` is called.
269269///
......@@ -284,7 +284,7 @@ var static_single_threaded_io: Io.Threaded = .init_single_threaded;
284284/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
285285/// application's chosen `Io` implementation.
286286pub fn lockStderr(buffer: []u8) Io.LockedStderr {
287 return static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) {
287 return static_single_threaded_io.lockStderr(buffer, null) catch |err| switch (err) {
288288 // Impossible to cancel because no calls to cancel using
289289 // `static_single_threaded_io` exist.
290290 error.Canceled => unreachable,
......@@ -292,7 +292,7 @@ pub fn lockStderr(buffer: []u8) Io.LockedStderr {
292292}
293293
294294pub fn unlockStderr() void {
295 static_single_threaded_io.ioBasic().unlockStderr();
295 static_single_threaded_io.unlockStderr();
296296}
297297
298298/// Writes to stderr, ignoring errors.
......@@ -630,7 +630,7 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
630630 defer it.deinit();
631631 if (!it.stratOk(options.allow_unsafe_unwind)) return empty_trace;
632632
633 const io = static_single_threaded_io.ioBasic();
633 const io = static_single_threaded_io;
634634
635635 var total_frames: usize = 0;
636636 var index: usize = 0;
......@@ -692,7 +692,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin
692692 var total_frames: usize = 0;
693693 var wait_for = options.first_address;
694694 var printed_any_frame = false;
695 const io = static_single_threaded_io.ioBasic();
695 const io = static_single_threaded_io;
696696 while (true) switch (it.next(io)) {
697697 .switch_to_fp => |unwind_error| {
698698 switch (StackIterator.fp_usability) {
......@@ -800,7 +800,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void
800800 return;
801801 },
802802 };
803 const io = static_single_threaded_io.ioBasic();
803 const io = static_single_threaded_io;
804804 const captured_frames = @min(n_frames, st.instruction_addresses.len);
805805 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
806806 // `ret_addr` is the return address, which is *after* the function call.
lib/std/debug/SelfInfo/Windows.zig+2-2
......@@ -315,8 +315,8 @@ const Module = struct {
315315 );
316316 if (len == 0) return error.MissingDebugInfo;
317317 const name_w = name_buffer[0 .. len + 4 :0];
318 var threaded: Io.Threaded = .init_single_threaded;
319 const coff_file = threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
318 // TODO eliminate the reference to Io.Threaded.global_single_threaded here
319 const coff_file = Io.Threaded.global_single_threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) {
320320 error.Canceled => |e| return e,
321321 error.Unexpected => |e| return e,
322322 error.FileNotFound => return error.MissingDebugInfo,
lib/std/dynamic_library.zig+1-3
......@@ -142,8 +142,6 @@ const ElfDynLibError = error{
142142 Streaming,
143143} || posix.OpenError || posix.MMapError;
144144
145var static_single_threaded_io: Io.Threaded = .init_single_threaded;
146
147145pub const ElfDynLib = struct {
148146 strings: [*:0]u8,
149147 syms: [*]elf.Sym,
......@@ -224,7 +222,7 @@ pub const ElfDynLib = struct {
224222
225223 /// Trusts the file. Malicious file will be able to execute arbitrary code.
226224 pub fn open(path: []const u8) Error!ElfDynLib {
227 const io = static_single_threaded_io.ioBasic();
225 const io = Io.Threaded.global_single_threaded.ioBasic();
228226
229227 const fd = try resolveFromName(io, path);
230228 defer posix.close(fd);
lib/std/process/Child.zig+7-8
......@@ -266,7 +266,7 @@ pub fn spawn(self: *Child, io: Io) SpawnError!void {
266266 }
267267
268268 if (native_os == .windows) {
269 return self.spawnWindows();
269 return self.spawnWindows(io);
270270 } else {
271271 return self.spawnPosix(io);
272272 }
......@@ -750,7 +750,7 @@ fn spawnPosix(self: *Child, io: Io) SpawnError!void {
750750 self.progress_node.setIpcFd(prog_pipe[0]);
751751}
752752
753fn spawnWindows(self: *Child) SpawnError!void {
753fn spawnWindows(self: *Child, io: Io) SpawnError!void {
754754 var saAttr = windows.SECURITY_ATTRIBUTES{
755755 .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES),
756756 .bInheritHandle = windows.TRUE,
......@@ -953,7 +953,7 @@ fn spawnWindows(self: *Child) SpawnError!void {
953953 try dir_buf.appendSlice(self.allocator, app_dir);
954954 }
955955
956 windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
956 windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| {
957957 const original_err = switch (no_path_err) {
958958 // argv[0] contains unsupported characters that will never resolve to a valid exe.
959959 error.InvalidArg0 => return error.FileNotFound,
......@@ -977,7 +977,7 @@ fn spawnWindows(self: *Child) SpawnError!void {
977977 dir_buf.clearRetainingCapacity();
978978 try dir_buf.appendSlice(self.allocator, search_path);
979979
980 if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
980 if (windowsCreateProcessPathExt(self.allocator, io, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) {
981981 break :run;
982982 } else |err| switch (err) {
983983 // 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);
10791079/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
10801080fn windowsCreateProcessPathExt(
10811081 allocator: Allocator,
1082 io: Io,
10821083 dir_buf: *ArrayList(u16),
10831084 app_buf: *ArrayList(u16),
10841085 pathext: [:0]const u16,
......@@ -1122,16 +1123,14 @@ fn windowsCreateProcessPathExt(
11221123 // Under those conditions, here we will have access to lower level directory
11231124 // opening function knowing which implementation we are in. Here, we imitate
11241125 // that scenario.
1125 var threaded: std.Io.Threaded = .init_single_threaded;
1126 const io = threaded.ioBasic();
1127
11281126 var dir = dir: {
11291127 // needs to be null-terminated
11301128 try dir_buf.append(allocator, 0);
11311129 defer dir_buf.shrinkRetainingCapacity(dir_path_len);
11321130 const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0];
11331131 const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z);
1134 break :dir threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
1132 // TODO eliminate this reference
1133 break :dir Io.Threaded.global_single_threaded.dirOpenDirWindows(.cwd(), prefixed_path.span(), .{
11351134 .iterate = true,
11361135 }) catch return error.FileNotFound;
11371136 };
test/incremental/no_change_preserves_tag_names+2-2
......@@ -8,7 +8,7 @@
88const std = @import("std");
99var some_enum: enum { first, second } = .first;
1010pub fn main() !void {
11 try std.Io.File.stdout().writeAll(@tagName(some_enum));
11 try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum));
1212}
1313#expect_stdout="first"
1414#update=no change
......@@ -16,6 +16,6 @@ pub fn main() !void {
1616const std = @import("std");
1717var some_enum: enum { first, second } = .first;
1818pub fn main() !void {
19 try std.Io.File.stdout().writeAll(@tagName(some_enum));
19 try std.Io.File.stdout().writeStreamingAll(std.Io.Threaded.global_single_threaded.ioBasic(), @tagName(some_enum));
2020}
2121#expect_stdout="first"
test/standalone/cmakedefine/check.zig+1-2
......@@ -9,8 +9,7 @@ pub fn main() !void {
99 const actual_path = args[1];
1010 const expected_path = args[2];
1111
12 var threaded: std.Io.Threaded = .init_single_threaded;
13 const io = threaded.io();
12 const io = std.Io.Threaded.global_single_threaded.ioBasic();
1413
1514 const actual = try std.Io.Dir.cwd().readFileAlloc(io, actual_path, arena, .limited(1024 * 1024));
1615 const expected = try std.Io.Dir.cwd().readFileAlloc(io, expected_path, arena, .limited(1024 * 1024));
test/standalone/dirname/exists_in.zig+1-2
......@@ -34,8 +34,7 @@ fn run(allocator: std.mem.Allocator) !void {
3434 return error.BadUsage;
3535 };
3636
37 var threaded: std.Io.Threaded = .init_single_threaded;
38 const io = threaded.io();
37 const io = std.Io.Threaded.global_single_threaded.ioBasic();
3938
4039 var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
4140 defer dir.close(io);
test/standalone/dirname/touch.zig+1-2
......@@ -29,8 +29,7 @@ fn run(allocator: std.mem.Allocator) !void {
2929 const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable;
3030 const basename = std.Io.Dir.path.basename(path);
3131
32 var threaded: std.Io.Threaded = .init_single_threaded;
33 const io = threaded.io();
32 const io = std.Io.Threaded.global_single_threaded.ioBasic();
3433
3534 var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{});
3635 defer dir.close(io);
test/standalone/entry_point/check_differ.zig+1-2
......@@ -6,8 +6,7 @@ pub fn main() !void {
66 const args = try std.process.argsAlloc(arena);
77 if (args.len != 3) return error.BadUsage; // usage: 'check_differ <path a> <path b>'
88
9 var threaded: std.Io.Threaded = .init_single_threaded;
10 const io = threaded.io();
9 const io = std.Io.Threaded.global_single_threaded.ioBasic();
1110
1211 const contents_1 = try std.Io.Dir.cwd().readFileAlloc(io, args[1], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
1312 const contents_2 = try std.Io.Dir.cwd().readFileAlloc(io, args[2], arena, .limited(1024 * 1024 * 64)); // 64 MiB ought to be plenty
test/standalone/install_headers/check_exists.zig+1-2
......@@ -11,8 +11,7 @@ pub fn main() !void {
1111 var arg_it = try std.process.argsWithAllocator(arena);
1212 _ = arg_it.next();
1313
14 var threaded: std.Io.Threaded = .init_single_threaded;
15 const io = threaded.io();
14 const io = std.Io.Threaded.global_single_threaded.ioBasic();
1615
1716 const cwd = std.Io.Dir.cwd();
1817 const cwd_realpath = try cwd.realPathAlloc(io, arena, ".");
test/standalone/posix/relpaths.zig+1-2
......@@ -14,8 +14,7 @@ pub fn main() !void {
1414 const gpa = debug_allocator.allocator();
1515 defer std.debug.assert(debug_allocator.deinit() == .ok);
1616
17 var threaded: std.Io.Threaded = .init_single_threaded;
18 const io = threaded.io();
17 const io = std.Io.Threaded.global_single_threaded.ioBasic();
1918
2019 // TODO this API isn't supposed to be used outside of unit testing. make it compilation error if used
2120 // outside of unit testing.
test/standalone/run_cwd/check_file_exists.zig+1-2
......@@ -8,8 +8,7 @@ pub fn main() !void {
88 if (args.len != 2) return error.BadUsage;
99 const path = args[1];
1010
11 var threaded: std.Io.Threaded = .init_single_threaded;
12 const io = threaded.io();
11 const io = std.Io.Threaded.global_single_threaded.ioBasic();
1312
1413 std.Io.Dir.cwd().access(io, path, .{}) catch return error.AccessFailed;
1514}
test/standalone/run_output_caching/main.zig+1-2
......@@ -1,8 +1,7 @@
11const std = @import("std");
22
33pub fn main() !void {
4 var threaded: std.Io.Threaded = .init_single_threaded;
5 const io = threaded.io();
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();
65 var args = try std.process.argsWithAllocator(std.heap.page_allocator);
76 _ = args.skip();
87 const filename = args.next().?;
test/standalone/run_output_paths/create_file.zig+1-2
......@@ -1,8 +1,7 @@
11const std = @import("std");
22
33pub fn main() !void {
4 var threaded: std.Io.Threaded = .init_single_threaded;
5 const io = threaded.io();
4 const io = std.Io.Threaded.global_single_threaded.ioBasic();
65 var args = try std.process.argsWithAllocator(std.heap.page_allocator);
76 _ = args.skip();
87 const dir_name = args.next().?;
test/standalone/self_exe_symlink/create-symlink.zig+1-2
......@@ -15,8 +15,7 @@ pub fn main() anyerror!void {
1515 const exe_rel_path = try std.fs.path.relative(allocator, std.fs.path.dirname(symlink_path) orelse ".", exe_path);
1616 defer allocator.free(exe_rel_path);
1717
18 var threaded: std.Io.Threaded = .init_single_threaded;
19 const io = threaded.io();
18 const io = std.Io.Threaded.global_single_threaded.ioBasic();
2019
2120 try std.Io.Dir.cwd().symLink(io, exe_rel_path, symlink_path, .{});
2221}
test/standalone/simple/hello_world/hello.zig+8-3
......@@ -1,8 +1,13 @@
11const std = @import("std");
22
3var static_single_threaded_io: std.Io.Threaded = .init_single_threaded;
4const io = static_single_threaded_io.ioBasic();
5
63pub fn main() !void {
4 var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
5 defer _ = debug_allocator.deinit();
6 const gpa = debug_allocator.allocator();
7
8 var threaded: std.Io.Threaded = .init(gpa);
9 defer threaded.deinit();
10 const io = threaded.io();
11
712 try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
813}
test/standalone/windows_paths/test.zig+1-2
......@@ -10,8 +10,7 @@ pub fn main() anyerror!void {
1010
1111 if (args.len < 2) return error.MissingArgs;
1212
13 var threaded: Io.Threaded = .init_single_threaded;
14 const io = threaded.io();
13 const io = std.Io.Threaded.global_single_threaded.ioBasic();
1514
1615 const exe_path = args[1];
1716