authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-17 15:47:33-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:09-08:00
logaa57793b680b3da05f1d888b4df15807905e57c8
treed88a1c6f56796942c9b21aee4bbb88e72045d298
parent97f106f949891870433bfcc6b7cf4c2a6709402e

std: rework locking stderr


10 files changed, 409 insertions(+), 469 deletions(-)

lib/std/Io.zig+26-20
......@@ -557,13 +557,6 @@ pub const net = @import("Io/net.zig");
557557userdata: ?*anyopaque,
558558vtable: *const VTable,
559559
560/// This is the global, process-wide protection to coordinate stderr writes.
561///
562/// The primary motivation for recursive mutex here is so that a panic while
563/// stderr mutex is held still dumps the stack trace and other debug
564/// information.
565pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
566
567560pub const VTable = struct {
568561 /// If it returns `null` it means `result` has been already populated and
569562 /// `await` will be a no-op.
......@@ -719,9 +712,9 @@ pub const VTable = struct {
719712
720713 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
721714 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
722 lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer,
723 tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer,
724 unlockStderrWriter: *const fn (?*anyopaque) void,
715 lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr,
716 tryLockStderr: *const fn (?*anyopaque, buffer: []u8) Cancelable!?LockedStderr,
717 unlockStderr: *const fn (?*anyopaque) void,
725718
726719 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
727720 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
......@@ -763,6 +756,7 @@ pub const UnexpectedError = error{
763756
764757pub const Dir = @import("Io/Dir.zig");
765758pub const File = @import("Io/File.zig");
759pub const Terminal = @import("Io/Terminal.zig");
766760
767761pub const Clock = enum {
768762 /// A settable system-wide clock that measures real (i.e. wall-clock)
......@@ -2177,22 +2171,34 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
21772171 }
21782172}
21792173
2174pub const LockedStderr = struct {
2175 file_writer: *File.Writer,
2176 terminal_mode: Terminal.Mode,
2177
2178 pub fn terminal(ls: LockedStderr) Terminal {
2179 return .{
2180 .writer = &ls.file_writer.interface,
2181 .mode = ls.terminal_mode,
2182 };
2183 }
2184};
2185
21802186/// For doing application-level writes to the standard error stream.
21812187/// Coordinates also with debug-level writes that are ignorant of Io interface
2182/// and implementations. When this returns, `stderr_thread_mutex` will be
2183/// locked.
2188/// and implementations. When this returns, `std.process.stderr_thread_mutex`
2189/// will be locked.
21842190///
21852191/// See also:
2186/// * `tryLockStderrWriter`
2187pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {
2188 return io.vtable.lockStderrWriter(io.userdata, buffer);
2192/// * `tryLockStderr`
2193pub fn lockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!LockedStderr {
2194 return io.vtable.lockStderr(io.userdata, buffer, terminal_mode);
21892195}
21902196
2191/// Same as `lockStderrWriter` but uncancelable and non-blocking.
2192pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {
2193 return io.vtable.tryLockStderrWriter(io.userdata, buffer);
2197/// Same as `lockStderr` but non-blocking.
2198pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr {
2199 return io.vtable.tryLockStderr(io.userdata, buffer, terminal_mode);
21942200}
21952201
2196pub fn unlockStderrWriter(io: Io) void {
2197 return io.vtable.unlockStderrWriter(io.userdata);
2202pub fn unlockStderr(io: Io) void {
2203 return io.vtable.unlockStderr(io.userdata);
21982204}
lib/std/Io/File/Reader.zig+17-17
......@@ -64,24 +64,24 @@ pub const Mode = enum {
6464 streaming,
6565 positional,
6666 /// Avoid syscalls other than `read` and `readv`.
67 streaming_reading,
67 streaming_simple,
6868 /// Avoid syscalls other than `pread` and `preadv`.
69 positional_reading,
69 positional_simple,
7070 /// Indicates reading cannot continue because of a seek failure.
7171 failure,
7272
7373 pub fn toStreaming(m: @This()) @This() {
7474 return switch (m) {
7575 .positional, .streaming => .streaming,
76 .positional_reading, .streaming_reading => .streaming_reading,
76 .positional_simple, .streaming_simple => .streaming_simple,
7777 .failure => .failure,
7878 };
7979 }
8080
81 pub fn toReading(m: @This()) @This() {
81 pub fn toSimple(m: @This()) @This() {
8282 return switch (m) {
83 .positional, .positional_reading => .positional_reading,
84 .streaming, .streaming_reading => .streaming_reading,
83 .positional, .positional_simple => .positional_simple,
84 .streaming, .streaming_simple => .streaming_simple,
8585 .failure => .failure,
8686 };
8787 }
......@@ -153,10 +153,10 @@ pub fn getSize(r: *Reader) SizeError!u64 {
153153pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
154154 const io = r.io;
155155 switch (r.mode) {
156 .positional, .positional_reading => {
156 .positional, .positional_simple => {
157157 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
158158 },
159 .streaming, .streaming_reading => {
159 .streaming, .streaming_simple => {
160160 const seek_err = r.seek_err orelse e: {
161161 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
162162 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
......@@ -183,10 +183,10 @@ pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
183183pub fn seekTo(r: *Reader, offset: u64) SeekError!void {
184184 const io = r.io;
185185 switch (r.mode) {
186 .positional, .positional_reading => {
186 .positional, .positional_simple => {
187187 setLogicalPos(r, offset);
188188 },
189 .streaming, .streaming_reading => {
189 .streaming, .streaming_simple => {
190190 const logical_pos = logicalPos(r);
191191 if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos));
192192 if (r.seek_err) |err| return err;
......@@ -225,19 +225,19 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea
225225 switch (mode) {
226226 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
227227 error.Unimplemented => {
228 r.mode = r.mode.toReading();
228 r.mode = r.mode.toSimple();
229229 return 0;
230230 },
231231 else => |e| return e,
232232 },
233 .positional_reading => {
233 .positional_simple => {
234234 const dest = limit.slice(try w.writableSliceGreedy(1));
235235 var data: [1][]u8 = .{dest};
236236 const n = try readVecPositional(r, &data);
237237 w.advance(n);
238238 return n;
239239 },
240 .streaming_reading => {
240 .streaming_simple => {
241241 const dest = limit.slice(try w.writableSliceGreedy(1));
242242 var data: [1][]u8 = .{dest};
243243 const n = try readVecStreaming(r, &data);
......@@ -251,8 +251,8 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea
251251fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
252252 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
253253 switch (r.mode) {
254 .positional, .positional_reading => return readVecPositional(r, data),
255 .streaming, .streaming_reading => return readVecStreaming(r, data),
254 .positional, .positional_simple => return readVecPositional(r, data),
255 .streaming, .streaming_simple => return readVecStreaming(r, data),
256256 .failure => return error.ReadFailed,
257257 }
258258}
......@@ -320,7 +320,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
320320 const io = r.io;
321321 const file = r.file;
322322 switch (r.mode) {
323 .positional, .positional_reading => {
323 .positional, .positional_simple => {
324324 const size = r.getSize() catch {
325325 r.mode = r.mode.toStreaming();
326326 return 0;
......@@ -330,7 +330,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
330330 setLogicalPos(r, logical_pos + delta);
331331 return delta;
332332 },
333 .streaming, .streaming_reading => {
333 .streaming, .streaming_simple => {
334334 // Unfortunately we can't seek forward without knowing the
335335 // size because the seek syscalls provided to us will not
336336 // return the true end position if a seek would exceed the
lib/std/Io/File/Writer.zig+3-247
......@@ -18,172 +18,7 @@ write_file_err: ?WriteFileError = null,
1818seek_err: ?SeekError = null,
1919interface: Io.Writer,
2020
21pub const Mode = union(enum) {
22 /// Uses `Io.VTable.fileWriteFileStreaming` if possible. Not a terminal.
23 /// `setColor` does nothing.
24 streaming,
25 /// Uses `Io.VTable.fileWriteFilePositional` if possible. Not a terminal.
26 /// `setColor` does nothing.
27 positional,
28 /// Avoids `Io.VTable.fileWriteFileStreaming`. Not a terminal. `setColor`
29 /// does nothing.
30 streaming_simple,
31 /// Avoids `Io.VTable.fileWriteFilePositional`. Not a terminal. `setColor`
32 /// does nothing.
33 positional_simple,
34 /// It's a terminal. Writes are escaped so as to strip escape sequences.
35 /// Color is enabled.
36 terminal_escaped,
37 /// It's a terminal. Colors are enabled via calling
38 /// SetConsoleTextAttribute. Writes are not escaped.
39 terminal_winapi: TerminalWinapi,
40 /// Indicates writing cannot continue because of a seek failure.
41 failure,
42
43 pub fn toStreaming(m: @This()) @This() {
44 return switch (m) {
45 .positional, .streaming => .streaming,
46 .positional_simple, .streaming_simple => .streaming_simple,
47 inline else => |_, x| x,
48 };
49 }
50
51 pub fn toSimple(m: @This()) @This() {
52 return switch (m) {
53 .positional, .positional_simple => .positional_simple,
54 .streaming, .streaming_simple => .streaming_simple,
55 inline else => |_, x| x,
56 };
57 }
58
59 pub fn toUnescaped(m: @This()) @This() {
60 return switch (m) {
61 .terminal_escaped => .streaming_simple,
62 inline else => |_, x| x,
63 };
64 }
65
66 pub const TerminalWinapi = if (!is_windows) noreturn else struct {
67 handle: File.Handle,
68 reset_attributes: u16,
69 };
70
71 /// Detect suitable TTY configuration options for the given file (commonly
72 /// stdout/stderr).
73 ///
74 /// Will attempt to enable ANSI escape code support if necessary/possible.
75 pub fn detect(io: Io, file: File, want_color: bool, fallback: Mode) Io.Cancelable!Mode {
76 if (!want_color) return if (try file.isTty(io)) .terminal_escaped else fallback;
77
78 if (file.enableAnsiEscapeCodes(io)) |_| {
79 return .terminal_escaped;
80 } else |err| switch (err) {
81 error.Canceled => return error.Canceled,
82 error.NotTerminalDevice, error.Unexpected => {},
83 }
84
85 if (is_windows and file.isTty(io)) {
86 const windows = std.os.windows;
87 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
88 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) {
89 return .{ .terminal_winapi = .{
90 .handle = file.handle,
91 .reset_attributes = info.wAttributes,
92 } };
93 }
94 return .terminal_escaped;
95 }
96
97 return fallback;
98 }
99
100 pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
101
102 pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) SetColorError!void {
103 switch (mode) {
104 .streaming, .positional, .streaming_simple, .positional_simple, .failure => return,
105 .terminal_escaped => {
106 const color_string = switch (color) {
107 .black => "\x1b[30m",
108 .red => "\x1b[31m",
109 .green => "\x1b[32m",
110 .yellow => "\x1b[33m",
111 .blue => "\x1b[34m",
112 .magenta => "\x1b[35m",
113 .cyan => "\x1b[36m",
114 .white => "\x1b[37m",
115 .bright_black => "\x1b[90m",
116 .bright_red => "\x1b[91m",
117 .bright_green => "\x1b[92m",
118 .bright_yellow => "\x1b[93m",
119 .bright_blue => "\x1b[94m",
120 .bright_magenta => "\x1b[95m",
121 .bright_cyan => "\x1b[96m",
122 .bright_white => "\x1b[97m",
123 .bold => "\x1b[1m",
124 .dim => "\x1b[2m",
125 .reset => "\x1b[0m",
126 };
127 try io_w.writeAll(color_string);
128 },
129 .terminal_winapi => |ctx| {
130 const windows = std.os.windows;
131 const attributes: windows.WORD = switch (color) {
132 .black => 0,
133 .red => windows.FOREGROUND_RED,
134 .green => windows.FOREGROUND_GREEN,
135 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
136 .blue => windows.FOREGROUND_BLUE,
137 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
138 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
139 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
140 .bright_black => windows.FOREGROUND_INTENSITY,
141 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
142 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
143 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
144 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
145 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
146 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
147 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
148 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
149 // This matches the old behavior of TTY.Color before the bright variants were added.
150 .dim => windows.FOREGROUND_INTENSITY,
151 .reset => ctx.reset_attributes,
152 };
153 try io_w.flush();
154 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
155 },
156 }
157 }
158
159 fn DecorateArgs(comptime Args: type) type {
160 const fields = @typeInfo(Args).@"struct".fields;
161 var new_fields: [fields.len]type = undefined;
162 for (fields, &new_fields) |old, *new| {
163 if (old.type == std.debug.FormatStackTrace) {
164 new.* = std.debug.FormatStackTrace.Decorated;
165 } else {
166 new.* = old.type;
167 }
168 }
169 return @Tuple(&new_fields);
170 }
171
172 pub fn decorateArgs(file_writer_mode: std.Io.File.Writer.Mode, args: anytype) DecorateArgs(@TypeOf(args)) {
173 var new_args: DecorateArgs(@TypeOf(args)) = undefined;
174 inline for (args, &new_args) |old, *new| {
175 if (@TypeOf(old) == std.debug.FormatStackTrace) {
176 new.* = .{
177 .stack_trace = old.stack_trace,
178 .file_writer_mode = file_writer_mode,
179 };
180 } else {
181 new.* = old;
182 }
183 }
184 return new_args;
185 }
186};
21pub const Mode = File.Reader.Mode;
18722
18823pub const Error = error{
18924 DiskQuota,
......@@ -277,8 +112,7 @@ pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer
277112 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
278113 switch (w.mode) {
279114 .positional, .positional_simple => return drainPositional(w, data, splat),
280 .streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat),
281 .terminal_escaped => return drainEscaping(w, data, splat),
115 .streaming, .streaming_simple => return drainStreaming(w, data, splat),
282116 .failure => return error.WriteFailed,
283117 }
284118}
......@@ -319,38 +153,13 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.
319153 return w.interface.consume(n);
320154}
321155
322fn findTerminalEscape(buffer: []const u8) ?usize {
323 return std.mem.findScalar(u8, buffer, 0x1b);
324}
325
326fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
327 const io = w.io;
328 const header = w.interface.buffered();
329 if (findTerminalEscape(header)) |i| {
330 _ = i;
331 // TODO strip terminal escape sequences here
332 }
333 for (data) |d| {
334 if (findTerminalEscape(d)) |i| {
335 _ = i;
336 // TODO strip terminal escape sequences here
337 }
338 }
339 const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
340 w.err = err;
341 return error.WriteFailed;
342 };
343 w.pos += n;
344 return w.interface.consume(n);
345}
346
347156pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
348157 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
349158 switch (w.mode) {
350159 .positional => return sendFilePositional(w, file_reader, limit),
351160 .positional_simple => return error.Unimplemented,
352161 .streaming => return sendFileStreaming(w, file_reader, limit),
353 .streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented,
162 .streaming_simple => return error.Unimplemented,
354163 .failure => return error.WriteFailed,
355164 }
356165}
......@@ -454,60 +263,7 @@ pub fn end(w: *Writer) EndError!void {
454263
455264 .streaming,
456265 .streaming_simple,
457 .terminal_escaped,
458 .terminal_winapi,
459266 .failure,
460267 => {},
461268 }
462269}
463
464pub const Color = enum {
465 black,
466 red,
467 green,
468 yellow,
469 blue,
470 magenta,
471 cyan,
472 white,
473 bright_black,
474 bright_red,
475 bright_green,
476 bright_yellow,
477 bright_blue,
478 bright_magenta,
479 bright_cyan,
480 bright_white,
481 dim,
482 bold,
483 reset,
484};
485
486pub fn setColor(w: *Writer, color: Color) Io.Writer.Error!void {
487 return w.mode.setColor(&w.interface, color) catch |err| switch (err) {
488 error.WriteFailed => |e| return e,
489 else => |e| w.err = e,
490 };
491}
492
493pub fn disableEscape(w: *Writer) Mode {
494 const prev = w.mode;
495 w.mode = w.mode.toUnescaped();
496 return prev;
497}
498
499pub fn restoreEscape(w: *Writer, mode: Mode) void {
500 w.mode = mode;
501}
502
503pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Writer.Error!void {
504 const prev_mode = w.disableEscape();
505 defer w.restoreEscape(prev_mode);
506 return w.interface.writeAll(bytes);
507}
508
509pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void {
510 const prev_mode = w.disableEscape();
511 defer w.restoreEscape(prev_mode);
512 return w.interface.print(fmt, args);
513}
lib/std/Io/Terminal.zig created+154
......@@ -0,0 +1,154 @@
1/// Abstraction for writing to a stream that might support terminal escape
2/// codes.
3const Terminal = @This();
4
5const builtin = @import("builtin");
6const is_windows = builtin.os.tag == .windows;
7
8const std = @import("std");
9const Io = std.Io;
10const File = std.Io.File;
11
12writer: *Io.Writer,
13mode: Mode,
14
15pub const Color = enum {
16 black,
17 red,
18 green,
19 yellow,
20 blue,
21 magenta,
22 cyan,
23 white,
24 bright_black,
25 bright_red,
26 bright_green,
27 bright_yellow,
28 bright_blue,
29 bright_magenta,
30 bright_cyan,
31 bright_white,
32 dim,
33 bold,
34 reset,
35};
36
37pub const Mode = union(enum) {
38 no_color,
39 escape_codes,
40 windows_api: WindowsApi,
41
42 pub const WindowsApi = if (!is_windows) noreturn else struct {
43 handle: File.Handle,
44 reset_attributes: u16,
45 };
46
47 /// Detect suitable TTY configuration options for the given file (commonly
48 /// stdout/stderr).
49 ///
50 /// Will attempt to enable ANSI escape code support if necessary/possible.
51 pub fn detect(io: Io, file: File) Io.Cancelable!Mode {
52 if (file.enableAnsiEscapeCodes(io)) |_| {
53 return .escape_codes;
54 } else |err| switch (err) {
55 error.Canceled => return error.Canceled,
56 error.NotTerminalDevice, error.Unexpected => {},
57 }
58
59 if (is_windows and file.isTty(io)) {
60 const windows = std.os.windows;
61 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
62 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) {
63 return .{ .terminal_winapi = .{
64 .handle = file.handle,
65 .reset_attributes = info.wAttributes,
66 } };
67 }
68 return .escape_codes;
69 }
70
71 return .no_color;
72 }
73};
74
75pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
76
77pub fn setColor(t: Terminal, color: Color) Io.Writer.Error!void {
78 switch (t.mode) {
79 .no_color => return,
80 .escape_codes => {
81 const color_string = switch (color) {
82 .black => "\x1b[30m",
83 .red => "\x1b[31m",
84 .green => "\x1b[32m",
85 .yellow => "\x1b[33m",
86 .blue => "\x1b[34m",
87 .magenta => "\x1b[35m",
88 .cyan => "\x1b[36m",
89 .white => "\x1b[37m",
90 .bright_black => "\x1b[90m",
91 .bright_red => "\x1b[91m",
92 .bright_green => "\x1b[92m",
93 .bright_yellow => "\x1b[93m",
94 .bright_blue => "\x1b[94m",
95 .bright_magenta => "\x1b[95m",
96 .bright_cyan => "\x1b[96m",
97 .bright_white => "\x1b[97m",
98 .bold => "\x1b[1m",
99 .dim => "\x1b[2m",
100 .reset => "\x1b[0m",
101 };
102 try t.writer.writeAll(color_string);
103 },
104 .windows_api => |wa| {
105 const windows = std.os.windows;
106 const attributes: windows.WORD = switch (color) {
107 .black => 0,
108 .red => windows.FOREGROUND_RED,
109 .green => windows.FOREGROUND_GREEN,
110 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
111 .blue => windows.FOREGROUND_BLUE,
112 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
113 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
114 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
115 .bright_black => windows.FOREGROUND_INTENSITY,
116 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
117 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
118 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
119 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
120 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
121 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
122 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
123 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
124 // This matches the old behavior of TTY.Color before the bright variants were added.
125 .dim => windows.FOREGROUND_INTENSITY,
126 .reset => wa.reset_attributes,
127 };
128 try t.writer.flush();
129 try windows.SetConsoleTextAttribute(wa.handle, attributes);
130 },
131 }
132}
133
134pub fn disableEscape(t: *Terminal) Mode {
135 const prev = t.mode;
136 t.mode = t.mode.toUnescaped();
137 return prev;
138}
139
140pub fn restoreEscape(t: *Terminal, mode: Mode) void {
141 t.mode = mode;
142}
143
144pub fn writeAllUnescaped(t: *Terminal, bytes: []const u8) Io.Writer.Error!void {
145 const prev_mode = t.disableEscape();
146 defer t.restoreEscape(prev_mode);
147 return t.interface.writeAll(bytes);
148}
149
150pub fn printUnescaped(t: *Terminal, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void {
151 const prev_mode = t.disableEscape();
152 defer t.restoreEscape(prev_mode);
153 return t.interface.print(fmt, args);
154}
lib/std/Io/Threaded.zig+53-30
......@@ -82,8 +82,8 @@ stderr_writer: File.Writer = .{
8282 .io = undefined,
8383 .interface = Io.File.Writer.initInterface(&.{}),
8484 .file = if (is_windows) undefined else .stderr(),
85 .mode = undefined,
8685},
86stderr_mode: Io.Terminal.Mode = .no_color,
8787stderr_writer_initialized: bool = false,
8888
8989pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
......@@ -755,9 +755,9 @@ pub fn io(t: *Threaded) Io {
755755
756756 .processExecutableOpen = processExecutableOpen,
757757 .processExecutablePath = processExecutablePath,
758 .lockStderrWriter = lockStderrWriter,
759 .tryLockStderrWriter = tryLockStderrWriter,
760 .unlockStderrWriter = unlockStderrWriter,
758 .lockStderr = lockStderr,
759 .tryLockStderr = tryLockStderr,
760 .unlockStderr = unlockStderr,
761761
762762 .now = now,
763763 .sleep = sleep,
......@@ -887,9 +887,9 @@ pub fn ioBasic(t: *Threaded) Io {
887887
888888 .processExecutableOpen = processExecutableOpen,
889889 .processExecutablePath = processExecutablePath,
890 .lockStderrWriter = lockStderrWriter,
891 .tryLockStderrWriter = tryLockStderrWriter,
892 .unlockStderrWriter = unlockStderrWriter,
890 .lockStderr = lockStderr,
891 .tryLockStderr = tryLockStderr,
892 .unlockStderr = unlockStderr,
893893
894894 .now = now,
895895 .sleep = sleep,
......@@ -10090,47 +10090,70 @@ fn netLookupFallible(
1009010090 return error.OptionUnsupported;
1009110091}
1009210092
10093fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer {
10093fn lockStderr(
10094 userdata: ?*anyopaque,
10095 buffer: []u8,
10096 terminal_mode: ?Io.Terminal.Mode,
10097) Io.Cancelable!Io.LockedStderr {
1009410098 const t: *Threaded = @ptrCast(@alignCast(userdata));
1009510099 // Only global mutex since this is Threaded.
10096 Io.stderr_thread_mutex.lock();
10097 if (!t.stderr_writer_initialized) {
10098 const io_t = ioBasic(t);
10099 if (is_windows) t.stderr_writer.file = .stderr();
10100 t.stderr_writer.io = io_t;
10101 t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple);
10102 t.stderr_writer_initialized = true;
10103 }
10104 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
10105 t.stderr_writer.interface.flush() catch {};
10106 t.stderr_writer.interface.buffer = buffer;
10107 return &t.stderr_writer;
10100 std.process.stderr_thread_mutex.lock();
10101 return initLockedStderr(t, buffer, terminal_mode);
1010810102}
1010910103
10110fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer {
10104fn tryLockStderr(
10105 userdata: ?*anyopaque,
10106 buffer: []u8,
10107 terminal_mode: ?Io.Terminal.Mode,
10108) Io.Cancelable!?Io.LockedStderr {
1011110109 const t: *Threaded = @ptrCast(@alignCast(userdata));
1011210110 // Only global mutex since this is Threaded.
10113 if (!Io.stderr_thread_mutex.tryLock()) return null;
10111 if (!std.process.stderr_thread_mutex.tryLock()) return null;
10112 return try initLockedStderr(t, buffer, terminal_mode);
10113}
10114
10115fn initLockedStderr(
10116 t: *Threaded,
10117 buffer: []u8,
10118 terminal_mode: ?Io.Terminal.Mode,
10119) Io.Cancelable!Io.LockedStderr {
1011410120 if (!t.stderr_writer_initialized) {
1011510121 const io_t = ioBasic(t);
1011610122 if (is_windows) t.stderr_writer.file = .stderr();
1011710123 t.stderr_writer.io = io_t;
10118 t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch
10119 return null;
1012010124 t.stderr_writer_initialized = true;
10125 t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file);
1012110126 }
10122 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
10123 t.stderr_writer.interface.flush() catch {};
10127 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) {
10128 error.WriteFailed => switch (t.stderr_writer.err.?) {
10129 error.Canceled => |e| return e,
10130 else => {},
10131 },
10132 };
10133 t.stderr_writer.interface.flush() catch |err| switch (err) {
10134 error.WriteFailed => switch (t.stderr_writer.err.?) {
10135 error.Canceled => |e| return e,
10136 else => {},
10137 },
10138 };
1012410139 t.stderr_writer.interface.buffer = buffer;
10125 return &t.stderr_writer;
10140 return .{
10141 .file_writer = &t.stderr_writer,
10142 .terminal_mode = t.stderr_mode,
10143 };
1012610144}
1012710145
10128fn unlockStderrWriter(userdata: ?*anyopaque) void {
10146fn unlockStderr(userdata: ?*anyopaque) void {
1012910147 const t: *Threaded = @ptrCast(@alignCast(userdata));
10130 t.stderr_writer.interface.flush() catch {};
10148 t.stderr_writer.interface.flush() catch |err| switch (err) {
10149 error.WriteFailed => switch (t.stderr_writer.err.?) {
10150 error.Canceled => @panic("TODO make this uncancelable"),
10151 else => {},
10152 },
10153 };
1013110154 t.stderr_writer.interface.end = 0;
1013210155 t.stderr_writer.interface.buffer = &.{};
10133 Io.stderr_thread_mutex.unlock();
10156 std.process.stderr_thread_mutex.unlock();
1013410157}
1013510158
1013610159pub const PosixAddress = extern union {
lib/std/Io/Writer.zig+1-1
......@@ -961,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE
961961 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
962962 error.EndOfStream => break,
963963 error.Unimplemented => {
964 file_reader.mode = file_reader.mode.toReading();
964 file_reader.mode = file_reader.mode.toSimple();
965965 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
966966 break;
967967 },
lib/std/Progress.zig+18-18
......@@ -565,10 +565,10 @@ fn updateThreadRun(io: Io) void {
565565 maybeUpdateSize(resize_flag);
566566
567567 const buffer, _ = computeRedraw(&serialized_buffer);
568 if (io.tryLockStderrWriter(&.{})) |fw| {
569 defer io.unlockStderrWriter();
568 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
569 defer io.unlockStderr();
570570 global_progress.need_clear = true;
571 fw.writeAllUnescaped(buffer) catch return;
571 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
572572 }
573573 }
574574
......@@ -576,18 +576,18 @@ fn updateThreadRun(io: Io) void {
576576 const resize_flag = wait(io, global_progress.refresh_rate_ns);
577577
578578 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
579 const fw = io.lockStderrWriter(&.{}) catch return;
580 defer io.unlockStderrWriter();
581 return clearWrittenWithEscapeCodes(fw) catch {};
579 const stderr = io.lockStderr(&.{}, null) catch return;
580 defer io.unlockStderr();
581 return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
582582 }
583583
584584 maybeUpdateSize(resize_flag);
585585
586586 const buffer, _ = computeRedraw(&serialized_buffer);
587 if (io.tryLockStderrWriter(&.{})) |fw| {
588 defer io.unlockStderrWriter();
587 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
588 defer io.unlockStderr();
589589 global_progress.need_clear = true;
590 fw.writeAllUnescaped(buffer) catch return;
590 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
591591 }
592592 }
593593}
......@@ -609,11 +609,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {
609609 maybeUpdateSize(resize_flag);
610610
611611 const buffer, const nl_n = computeRedraw(&serialized_buffer);
612 if (io.tryLockStderrWriter()) |fw| {
613 defer io.unlockStderrWriter();
612 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
613 defer io.unlockStderr();
614614 windowsApiWriteMarker();
615615 global_progress.need_clear = true;
616 fw.writeAllUnescaped(buffer) catch return;
616 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
617617 windowsApiMoveToMarker(nl_n) catch return;
618618 }
619619 }
......@@ -622,20 +622,20 @@ fn windowsApiUpdateThreadRun(io: Io) void {
622622 const resize_flag = wait(io, global_progress.refresh_rate_ns);
623623
624624 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
625 _ = io.lockStderrWriter() catch return;
626 defer io.unlockStderrWriter();
625 _ = io.lockStderr(&.{}, null) catch return;
626 defer io.unlockStderr();
627627 return clearWrittenWindowsApi() catch {};
628628 }
629629
630630 maybeUpdateSize(resize_flag);
631631
632632 const buffer, const nl_n = computeRedraw(&serialized_buffer);
633 if (io.tryLockStderrWriter()) |fw| {
634 defer io.unlockStderrWriter();
633 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
634 defer io.unlockStderr();
635635 clearWrittenWindowsApi() catch return;
636636 windowsApiWriteMarker();
637637 global_progress.need_clear = true;
638 fw.writeAllUnescaped(buffer) catch return;
638 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
639639 windowsApiMoveToMarker(nl_n) catch return;
640640 }
641641 }
......@@ -766,7 +766,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
766766
767767pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void {
768768 if (noop_impl or !global_progress.need_clear) return;
769 try file_writer.writeAllUnescaped(clear ++ progress_remove);
769 try file_writer.interface.writeAll(clear ++ progress_remove);
770770 global_progress.need_clear = false;
771771}
772772
lib/std/debug.zig+114-118
......@@ -265,29 +265,34 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
265265/// separate from the application's `Io` instance.
266266var static_single_threaded_io: Io.Threaded = .init_single_threaded;
267267
268/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
268/// Allows the caller to freely write to stderr until `unlockStderr` is called.
269269///
270270/// During the lock, any `std.Progress` information is cleared from the terminal.
271271///
272/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple
273/// times. The primary motivation is that this allows the panic handler to safely dump the stack
274/// trace and panic message even if the mutex was held at the panic site.
272/// The lock is recursive, so it is valid for the same thread to call
273/// `lockStderr` multiple times, allowing the panic handler to safely
274/// dump the stack trace and panic message even if the mutex was held at the
275/// panic site.
275276///
276277/// The returned `Writer` does not need to be manually flushed: flushing is
277/// performed automatically when the matching `unlockStderrWriter` call occurs.
278/// performed automatically when the matching `unlockStderr` call occurs.
278279///
279280/// This is a low-level debugging primitive that bypasses the `Io` interface,
280281/// writing directly to stderr using the most basic syscalls available. This
281282/// function does not switch threads, switch stacks, or suspend.
282283///
283/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with
284/// the application's chosen `Io` implementation.
285pub fn lockStderrWriter(buffer: []u8) *File.Writer {
286 return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable;
284/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
285/// application's chosen `Io` implementation.
286pub fn lockStderr(buffer: []u8) Io.Terminal {
287 return (static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) {
288 // Impossible to cancel because no calls to cancel using
289 // `static_single_threaded_io` exist.
290 error.Canceled => unreachable,
291 }).terminal();
287292}
288293
289pub fn unlockStderrWriter() void {
290 static_single_threaded_io.ioBasic().unlockStderrWriter();
294pub fn unlockStderr() void {
295 static_single_threaded_io.ioBasic().unlockStderr();
291296}
292297
293298/// Writes to stderr, ignoring errors.
......@@ -299,14 +304,14 @@ pub fn unlockStderrWriter() void {
299304/// Uses a 64-byte buffer for formatted printing which is flushed before this
300305/// function returns.
301306///
302/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to
307/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to
303308/// integrate with the application's chosen `Io` implementation.
304309pub fn print(comptime fmt: []const u8, args: anytype) void {
305310 nosuspend {
306311 var buffer: [64]u8 = undefined;
307 const stderr = lockStderrWriter(&buffer);
308 defer unlockStderrWriter();
309 stderr.interface.print(fmt, stderr.mode.decorateArgs(args)) catch return;
312 const stderr = lockStderr(&buffer);
313 defer unlockStderr();
314 stderr.writer.print(fmt, args) catch return;
310315 }
311316}
312317
......@@ -322,43 +327,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo {
322327/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
323328/// Obtains the stderr mutex while dumping.
324329pub fn dumpHex(bytes: []const u8) void {
325 const bw, const ttyconf = lockStderrWriter(&.{});
326 defer unlockStderrWriter();
327 dumpHexFallible(bw, ttyconf, bytes) catch {};
330 const stderr = lockStderr(&.{});
331 defer unlockStderr();
332 dumpHexFallible(stderr, bytes) catch {};
328333}
329334
330335/// Prints a hexadecimal view of the bytes, returning any error that occurs.
331pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !void {
336pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void {
337 const w = t.writer;
332338 var chunks = mem.window(u8, bytes, 16, 16);
333339 while (chunks.next()) |window| {
334340 // 1. Print the address.
335341 const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;
336 try fwm.setColor(bw, .dim);
342 try t.setColor(.dim);
337343 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
338344 // Also, make sure all lines are aligned by padding the address.
339 try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
340 try fwm.setColor(bw, .reset);
345 try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
346 try t.setColor(.reset);
341347
342348 // 2. Print the bytes.
343349 for (window, 0..) |byte, index| {
344 try bw.print("{X:0>2} ", .{byte});
345 if (index == 7) try bw.writeByte(' ');
350 try w.print("{X:0>2} ", .{byte});
351 if (index == 7) try w.writeByte(' ');
346352 }
347 try bw.writeByte(' ');
353 try w.writeByte(' ');
348354 if (window.len < 16) {
349355 var missing_columns = (16 - window.len) * 3;
350356 if (window.len < 8) missing_columns += 1;
351 try bw.splatByteAll(' ', missing_columns);
357 try w.splatByteAll(' ', missing_columns);
352358 }
353359
354360 // 3. Print the characters.
355361 for (window) |byte| {
356362 if (std.ascii.isPrint(byte)) {
357 try bw.writeByte(byte);
363 try w.writeByte(byte);
358364 } else {
359365 // Related: https://github.com/ziglang/zig/issues/7600
360 if (fwm == .terminal_winapi) {
361 try bw.writeByte('.');
366 if (t.mode == .windows_api) {
367 try w.writeByte('.');
362368 continue;
363369 }
364370
......@@ -366,14 +372,14 @@ pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !v
366372 // We don't want to do this for all control codes because most control codes apart from
367373 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
368374 switch (byte) {
369 '\n' => try bw.writeAll("␊"),
370 '\r' => try bw.writeAll("␍"),
371 '\t' => try bw.writeAll("␉"),
372 else => try bw.writeByte('.'),
375 '\n' => try w.writeAll("␊"),
376 '\r' => try w.writeAll("␍"),
377 '\t' => try w.writeAll("␉"),
378 else => try w.writeByte('.'),
373379 }
374380 }
375381 }
376 try bw.writeByte('\n');
382 try w.writeByte('\n');
377383 }
378384}
379385
......@@ -545,26 +551,27 @@ pub fn defaultPanic(
545551 _ = panicking.fetchAdd(1, .seq_cst);
546552
547553 trace: {
548 const stderr = lockStderrWriter(&.{});
549 defer unlockStderrWriter();
554 const stderr = lockStderr(&.{});
555 defer unlockStderr();
556 const writer = stderr.writer;
550557
551558 if (builtin.single_threaded) {
552 stderr.interface.print("panic: ", .{}) catch break :trace;
559 writer.print("panic: ", .{}) catch break :trace;
553560 } else {
554561 const current_thread_id = std.Thread.getCurrentId();
555 stderr.interface.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
562 writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
556563 }
557 stderr.interface.print("{s}\n", .{msg}) catch break :trace;
564 writer.print("{s}\n", .{msg}) catch break :trace;
558565
559566 if (@errorReturnTrace()) |t| if (t.index > 0) {
560 stderr.interface.writeAll("error return context:\n") catch break :trace;
561 writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace;
562 stderr.interface.writeAll("\nstack trace:\n") catch break :trace;
567 writer.writeAll("error return context:\n") catch break :trace;
568 writeStackTrace(t, stderr) catch break :trace;
569 writer.writeAll("\nstack trace:\n") catch break :trace;
563570 };
564571 writeCurrentStackTrace(.{
565572 .first_address = first_trace_addr orelse @returnAddress(),
566573 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
567 }, &stderr.interface, stderr.mode) catch break :trace;
574 }, stderr) catch break :trace;
568575 }
569576
570577 waitForOtherThreadToFinishPanicking();
......@@ -574,8 +581,8 @@ pub fn defaultPanic(
574581 // A panic happened while trying to print a previous panic message.
575582 // We're still holding the mutex but that's fine as we're going to
576583 // call abort().
577 const stderr = lockStderrWriter(&.{});
578 stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
584 const stderr = lockStderr(&.{});
585 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
579586 },
580587 else => {}, // Panicked while printing the recursive panic message.
581588 }
......@@ -656,28 +663,29 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
656663/// Write the current stack trace to `writer`, annotated with source locations.
657664///
658665/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
659pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
666pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
667 const writer = t.writer;
660668 if (!std.options.allow_stack_tracing) {
661 fwm.setColor(writer, .dim) catch {};
669 t.setColor(.dim) catch {};
662670 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
663 fwm.setColor(writer, .reset) catch {};
671 t.setColor(.reset) catch {};
664672 return;
665673 }
666674 const di_gpa = getDebugInfoAllocator();
667675 const di = getSelfDebugInfo() catch |err| switch (err) {
668676 error.UnsupportedTarget => {
669 fwm.setColor(writer, .dim) catch {};
677 t.setColor(.dim) catch {};
670678 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
671 fwm.setColor(writer, .reset) catch {};
679 t.setColor(.reset) catch {};
672680 return;
673681 },
674682 };
675683 var it: StackIterator = .init(options.context);
676684 defer it.deinit();
677685 if (!it.stratOk(options.allow_unsafe_unwind)) {
678 fwm.setColor(writer, .dim) catch {};
686 t.setColor(.dim) catch {};
679687 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
680 fwm.setColor(writer, .reset) catch {};
688 t.setColor(.reset) catch {};
681689 return;
682690 }
683691 var total_frames: usize = 0;
......@@ -701,31 +709,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
701709 error.Unexpected => "unexpected error",
702710 };
703711 if (it.stratOk(options.allow_unsafe_unwind)) {
704 fwm.setColor(writer, .dim) catch {};
712 t.setColor(.dim) catch {};
705713 try writer.print(
706714 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
707715 .{ module_name, unwind_error.address, caption },
708716 );
709 fwm.setColor(writer, .reset) catch {};
717 t.setColor(.reset) catch {};
710718 } else {
711 fwm.setColor(writer, .dim) catch {};
719 t.setColor(.dim) catch {};
712720 try writer.print(
713721 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
714722 .{ module_name, unwind_error.address, caption },
715723 );
716 fwm.setColor(writer, .reset) catch {};
724 t.setColor(.reset) catch {};
717725 return;
718726 }
719727 },
720728 .end => break,
721729 .frame => |ret_addr| {
722730 if (total_frames > 10_000) {
723 fwm.setColor(writer, .dim) catch {};
731 t.setColor(.dim) catch {};
724732 try writer.print(
725733 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
726734 .{total_frames},
727735 );
728 fwm.setColor(writer, .reset) catch {};
736 t.setColor(.reset) catch {};
729737 return;
730738 }
731739 total_frames += 1;
......@@ -735,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
735743 }
736744 // `ret_addr` is the return address, which is *after* the function call.
737745 // Subtract 1 to get an address *in* the function call for a better source location.
738 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
746 try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
739747 printed_any_frame = true;
740748 },
741749 };
......@@ -743,8 +751,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
743751}
744752/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
745753pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
746 const stderr = lockStderrWriter(&.{});
747 defer unlockStderrWriter();
754 const stderr = lockStderr(&.{});
755 defer unlockStderr();
748756 writeCurrentStackTrace(.{
749757 .first_address = a: {
750758 if (options.first_address) |a| break :a a;
......@@ -753,38 +761,28 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
753761 },
754762 .context = options.context,
755763 .allow_unsafe_unwind = options.allow_unsafe_unwind,
756 }, &stderr.interface, stderr.mode) catch |err| switch (err) {
764 }, stderr) catch |err| switch (err) {
757765 error.WriteFailed => {},
758766 };
759767}
760768
761769pub const FormatStackTrace = struct {
762770 stack_trace: StackTrace,
771 terminal_mode: Io.Terminal.Mode = .no_color,
763772
764 pub const Decorated = struct {
765 stack_trace: StackTrace,
766 file_writer_mode: File.Writer.Mode,
767
768 pub fn format(decorated: Decorated, writer: *Writer) Writer.Error!void {
769 try writer.writeByte('\n');
770 try writeStackTrace(&decorated.stack_trace, writer, decorated.file_writer_mode);
771 }
772 };
773
774 pub fn format(context: FormatStackTrace, writer: *Writer) Writer.Error!void {
775 return Decorated.format(.{
776 .stack_trace = context.stack_trace,
777 .file_writer_mode = .streaming,
778 }, writer);
773 pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void {
774 try writer.writeByte('\n');
775 try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode });
779776 }
780777};
781778
782779/// Write a previously captured stack trace to `writer`, annotated with source locations.
783pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
780pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void {
781 const writer = t.writer;
784782 if (!std.options.allow_stack_tracing) {
785 fwm.setColor(writer, .dim) catch {};
783 t.setColor(.dim) catch {};
786784 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
787 fwm.setColor(writer, .reset) catch {};
785 t.setColor(.reset) catch {};
788786 return;
789787 }
790788
......@@ -795,9 +793,9 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.
795793 const di_gpa = getDebugInfoAllocator();
796794 const di = getSelfDebugInfo() catch |err| switch (err) {
797795 error.UnsupportedTarget => {
798 fwm.setColor(writer, .dim) catch {};
796 t.setColor(.dim) catch {};
799797 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
800 fwm.setColor(writer, .reset) catch {};
798 t.setColor(.reset) catch {};
801799 return;
802800 },
803801 };
......@@ -806,19 +804,19 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.
806804 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
807805 // `ret_addr` is the return address, which is *after* the function call.
808806 // Subtract 1 to get an address *in* the function call for a better source location.
809 try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
807 try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
810808 }
811809 if (n_frames > captured_frames) {
812 fwm.setColor(writer, .bold) catch {};
810 t.setColor(.bold) catch {};
813811 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
814 fwm.setColor(writer, .reset) catch {};
812 t.setColor(.reset) catch {};
815813 }
816814}
817815/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
818816pub fn dumpStackTrace(st: *const StackTrace) void {
819 const stderr = lockStderrWriter(&.{});
820 defer unlockStderrWriter();
821 writeStackTrace(st, &stderr.interface, stderr.mode) catch |err| switch (err) {
817 const stderr = lockStderr(&.{});
818 defer unlockStderr();
819 writeStackTrace(st, stderr) catch |err| switch (err) {
822820 error.WriteFailed => {},
823821 };
824822}
......@@ -1117,9 +1115,8 @@ fn printSourceAtAddress(
11171115 gpa: Allocator,
11181116 io: Io,
11191117 debug_info: *SelfInfo,
1120 writer: *Writer,
1118 t: Io.Terminal,
11211119 address: usize,
1122 fwm: File.Writer.Mode,
11231120) Writer.Error!void {
11241121 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
11251122 error.MissingDebugInfo,
......@@ -1127,40 +1124,39 @@ fn printSourceAtAddress(
11271124 error.InvalidDebugInfo,
11281125 => .unknown,
11291126 error.ReadFailed, error.Unexpected, error.Canceled => s: {
1130 fwm.setColor(writer, .dim) catch {};
1131 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1132 fwm.setColor(writer, .reset) catch {};
1127 t.setColor(.dim) catch {};
1128 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1129 t.setColor(.reset) catch {};
11331130 break :s .unknown;
11341131 },
11351132 error.OutOfMemory => s: {
1136 fwm.setColor(writer, .dim) catch {};
1137 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1138 fwm.setColor(writer, .reset) catch {};
1133 t.setColor(.dim) catch {};
1134 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1135 t.setColor(.reset) catch {};
11391136 break :s .unknown;
11401137 },
11411138 };
11421139 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
11431140 return printLineInfo(
11441141 io,
1145 writer,
1142 t,
11461143 symbol.source_location,
11471144 address,
11481145 symbol.name orelse "???",
11491146 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
1150 fwm,
11511147 );
11521148}
11531149fn printLineInfo(
11541150 io: Io,
1155 writer: *Writer,
1151 t: Io.Terminal,
11561152 source_location: ?SourceLocation,
11571153 address: usize,
11581154 symbol_name: []const u8,
11591155 compile_unit_name: []const u8,
1160 fwm: File.Writer.Mode,
11611156) Writer.Error!void {
11621157 nosuspend {
1163 fwm.setColor(writer, .bold) catch {};
1158 const writer = t.writer;
1159 t.setColor(.bold) catch {};
11641160
11651161 if (source_location) |*sl| {
11661162 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
......@@ -1168,11 +1164,11 @@ fn printLineInfo(
11681164 try writer.writeAll("???:?:?");
11691165 }
11701166
1171 fwm.setColor(writer, .reset) catch {};
1167 t.setColor(.reset) catch {};
11721168 try writer.writeAll(": ");
1173 fwm.setColor(writer, .dim) catch {};
1169 t.setColor(.dim) catch {};
11741170 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
1175 fwm.setColor(writer, .reset) catch {};
1171 t.setColor(.reset) catch {};
11761172 try writer.writeAll("\n");
11771173
11781174 // Show the matching source code line if possible
......@@ -1183,9 +1179,9 @@ fn printLineInfo(
11831179 const space_needed = @as(usize, @intCast(sl.column - 1));
11841180
11851181 try writer.splatByteAll(' ', space_needed);
1186 fwm.setColor(writer, .green) catch {};
1182 t.setColor(.green) catch {};
11871183 try writer.writeAll("^");
1188 fwm.setColor(writer, .reset) catch {};
1184 t.setColor(.reset) catch {};
11891185 }
11901186 try writer.writeAll("\n");
11911187 } else |_| {
......@@ -1554,19 +1550,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
15541550 _ = panicking.fetchAdd(1, .seq_cst);
15551551
15561552 trace: {
1557 const stderr = lockStderrWriter(&.{});
1558 defer unlockStderrWriter();
1553 const stderr = lockStderr(&.{});
1554 defer unlockStderr();
15591555
15601556 if (addr) |a| {
1561 stderr.interface.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
1557 stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
15621558 } else {
1563 stderr.interface.print("{s} (no address available)\n", .{name}) catch break :trace;
1559 stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace;
15641560 }
15651561 if (opt_ctx) |context| {
15661562 writeCurrentStackTrace(.{
15671563 .context = context,
15681564 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
1569 }, &stderr.interface, stderr.mode) catch break :trace;
1565 }, stderr) catch break :trace;
15701566 }
15711567 }
15721568 },
......@@ -1575,8 +1571,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
15751571 // A segfault happened while trying to print a previous panic message.
15761572 // We're still holding the mutex but that's fine as we're going to
15771573 // call abort().
1578 const stderr = lockStderrWriter(&.{});
1579 stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
1574 const stderr = lockStderr(&.{});
1575 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
15801576 },
15811577 else => {}, // Panicked while printing the recursive panic message.
15821578 }
......@@ -1682,21 +1678,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16821678 pub fn dump(t: @This()) void {
16831679 if (!enabled) return;
16841680
1685 const stderr = lockStderrWriter(&.{});
1686 defer unlockStderrWriter();
1681 const stderr = lockStderr(&.{});
1682 defer unlockStderr();
16871683 const end = @min(t.index, size);
16881684 for (t.addrs[0..end], 0..) |frames_array, i| {
1689 stderr.interface.print("{s}:\n", .{t.notes[i]}) catch return;
1685 stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;
16901686 var frames_array_mutable = frames_array;
16911687 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
16921688 const stack_trace: StackTrace = .{
16931689 .index = frames.len,
16941690 .instruction_addresses = frames,
16951691 };
1696 writeStackTrace(&stack_trace, &stderr.interface, stderr.mode) catch return;
1692 writeStackTrace(&stack_trace, stderr) catch return;
16971693 }
16981694 if (t.index > end) {
1699 stderr.interface.print("{d} more traces not shown; consider increasing trace size\n", .{
1695 stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{
17001696 t.index - end,
17011697 }) catch return;
17021698 }
lib/std/log.zig+16-18
......@@ -92,35 +92,33 @@ pub fn defaultLog(
9292 args: anytype,
9393) void {
9494 var buffer: [64]u8 = undefined;
95 const stderr = std.debug.lockStderrWriter(&buffer);
96 defer std.debug.unlockStderrWriter();
97 return defaultLogFileWriter(level, scope, format, args, stderr);
95 const stderr = std.debug.lockStderr(&buffer);
96 defer std.debug.unlockStderr();
97 return defaultLogFileTerminal(level, scope, format, args, stderr) catch {};
9898}
9999
100pub fn defaultLogFileWriter(
100pub fn defaultLogFileTerminal(
101101 comptime level: Level,
102102 comptime scope: @EnumLiteral(),
103103 comptime format: []const u8,
104104 args: anytype,
105 fw: *std.Io.File.Writer,
106) void {
107 fw.setColor(switch (level) {
105 t: std.Io.Terminal,
106) std.Io.Writer.Error!void {
107 t.setColor(switch (level) {
108108 .err => .red,
109109 .warn => .yellow,
110110 .info => .green,
111111 .debug => .magenta,
112112 }) catch {};
113 fw.setColor(.bold) catch {};
114 fw.interface.writeAll(level.asText()) catch return;
115 fw.setColor(.reset) catch {};
116 fw.setColor(.dim) catch {};
117 fw.setColor(.bold) catch {};
118 if (scope != .default) {
119 fw.interface.print("({s})", .{@tagName(scope)}) catch return;
120 }
121 fw.interface.writeAll(": ") catch return;
122 fw.setColor(.reset) catch {};
123 fw.interface.print(format ++ "\n", fw.mode.decorateArgs(args)) catch return;
113 t.setColor(.bold) catch {};
114 try t.writer.writeAll(level.asText());
115 t.setColor(.reset) catch {};
116 t.setColor(.dim) catch {};
117 t.setColor(.bold) catch {};
118 if (scope != .default) try t.writer.print("({t})", .{scope});
119 try t.writer.writeAll(": ");
120 t.setColor(.reset) catch {};
121 try t.writer.print(format ++ "\n", args);
124122}
125123
126124/// Returns a scoped logging namespace that logs all messages using the scope
lib/std/process.zig+7
......@@ -21,6 +21,13 @@ pub const changeCurDirZ = posix.chdirZ;
2121
2222pub const GetCwdError = posix.GetCwdError;
2323
24/// This is the global, process-wide protection to coordinate stderr writes.
25///
26/// The primary motivation for recursive mutex here is so that a panic while
27/// stderr mutex is held still dumps the stack trace and other debug
28/// information.
29pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
30
2431/// The result is a slice of `out_buffer`, from index `0`.
2532/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
2633/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.