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");...@@ -557,13 +557,6 @@ pub const net = @import("Io/net.zig");
557userdata: ?*anyopaque,557userdata: ?*anyopaque,
558vtable: *const VTable,558vtable: *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
567pub const VTable = struct {560pub const VTable = struct {
568 /// If it returns `null` it means `result` has been already populated and561 /// If it returns `null` it means `result` has been already populated and
569 /// `await` will be a no-op.562 /// `await` will be a no-op.
...@@ -719,9 +712,9 @@ pub const VTable = struct {...@@ -719,9 +712,9 @@ pub const VTable = struct {
719712
720 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,713 processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
721 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,714 processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
722 lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer,715 lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr,
723 tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer,716 tryLockStderr: *const fn (?*anyopaque, buffer: []u8) Cancelable!?LockedStderr,
724 unlockStderrWriter: *const fn (?*anyopaque) void,717 unlockStderr: *const fn (?*anyopaque) void,
725718
726 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,719 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
727 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,720 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
...@@ -763,6 +756,7 @@ pub const UnexpectedError = error{...@@ -763,6 +756,7 @@ pub const UnexpectedError = error{
763756
764pub const Dir = @import("Io/Dir.zig");757pub const Dir = @import("Io/Dir.zig");
765pub const File = @import("Io/File.zig");758pub const File = @import("Io/File.zig");
759pub const Terminal = @import("Io/Terminal.zig");
766760
767pub const Clock = enum {761pub const Clock = enum {
768 /// A settable system-wide clock that measures real (i.e. wall-clock)762 /// 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)) {...@@ -2177,22 +2171,34 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
2177 }2171 }
2178}2172}
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
2180/// For doing application-level writes to the standard error stream.2186/// For doing application-level writes to the standard error stream.
2181/// Coordinates also with debug-level writes that are ignorant of Io interface2187/// Coordinates also with debug-level writes that are ignorant of Io interface
2182/// and implementations. When this returns, `stderr_thread_mutex` will be2188/// and implementations. When this returns, `std.process.stderr_thread_mutex`
2183/// locked.2189/// will be locked.
2184///2190///
2185/// See also:2191/// See also:
2186/// * `tryLockStderrWriter`2192/// * `tryLockStderr`
2187pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {2193pub fn lockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!LockedStderr {
2188 return io.vtable.lockStderrWriter(io.userdata, buffer);2194 return io.vtable.lockStderr(io.userdata, buffer, terminal_mode);
2189}2195}
21902196
2191/// Same as `lockStderrWriter` but uncancelable and non-blocking.2197/// Same as `lockStderr` but non-blocking.
2192pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {2198pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr {
2193 return io.vtable.tryLockStderrWriter(io.userdata, buffer);2199 return io.vtable.tryLockStderr(io.userdata, buffer, terminal_mode);
2194}2200}
21952201
2196pub fn unlockStderrWriter(io: Io) void {2202pub fn unlockStderr(io: Io) void {
2197 return io.vtable.unlockStderrWriter(io.userdata);2203 return io.vtable.unlockStderr(io.userdata);
2198}2204}
lib/std/Io/File/Reader.zig+17-17
...@@ -64,24 +64,24 @@ pub const Mode = enum {...@@ -64,24 +64,24 @@ pub const Mode = enum {
64 streaming,64 streaming,
65 positional,65 positional,
66 /// Avoid syscalls other than `read` and `readv`.66 /// Avoid syscalls other than `read` and `readv`.
67 streaming_reading,67 streaming_simple,
68 /// Avoid syscalls other than `pread` and `preadv`.68 /// Avoid syscalls other than `pread` and `preadv`.
69 positional_reading,69 positional_simple,
70 /// Indicates reading cannot continue because of a seek failure.70 /// Indicates reading cannot continue because of a seek failure.
71 failure,71 failure,
7272
73 pub fn toStreaming(m: @This()) @This() {73 pub fn toStreaming(m: @This()) @This() {
74 return switch (m) {74 return switch (m) {
75 .positional, .streaming => .streaming,75 .positional, .streaming => .streaming,
76 .positional_reading, .streaming_reading => .streaming_reading,76 .positional_simple, .streaming_simple => .streaming_simple,
77 .failure => .failure,77 .failure => .failure,
78 };78 };
79 }79 }
8080
81 pub fn toReading(m: @This()) @This() {81 pub fn toSimple(m: @This()) @This() {
82 return switch (m) {82 return switch (m) {
83 .positional, .positional_reading => .positional_reading,83 .positional, .positional_simple => .positional_simple,
84 .streaming, .streaming_reading => .streaming_reading,84 .streaming, .streaming_simple => .streaming_simple,
85 .failure => .failure,85 .failure => .failure,
86 };86 };
87 }87 }
...@@ -153,10 +153,10 @@ pub fn getSize(r: *Reader) SizeError!u64 {...@@ -153,10 +153,10 @@ pub fn getSize(r: *Reader) SizeError!u64 {
153pub fn seekBy(r: *Reader, offset: i64) SeekError!void {153pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
154 const io = r.io;154 const io = r.io;
155 switch (r.mode) {155 switch (r.mode) {
156 .positional, .positional_reading => {156 .positional, .positional_simple => {
157 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));157 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
158 },158 },
159 .streaming, .streaming_reading => {159 .streaming, .streaming_simple => {
160 const seek_err = r.seek_err orelse e: {160 const seek_err = r.seek_err orelse e: {
161 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {161 if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
162 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));162 setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
...@@ -183,10 +183,10 @@ pub fn seekBy(r: *Reader, offset: i64) SeekError!void {...@@ -183,10 +183,10 @@ pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
183pub fn seekTo(r: *Reader, offset: u64) SeekError!void {183pub fn seekTo(r: *Reader, offset: u64) SeekError!void {
184 const io = r.io;184 const io = r.io;
185 switch (r.mode) {185 switch (r.mode) {
186 .positional, .positional_reading => {186 .positional, .positional_simple => {
187 setLogicalPos(r, offset);187 setLogicalPos(r, offset);
188 },188 },
189 .streaming, .streaming_reading => {189 .streaming, .streaming_simple => {
190 const logical_pos = logicalPos(r);190 const logical_pos = logicalPos(r);
191 if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos));191 if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos));
192 if (r.seek_err) |err| return err;192 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...@@ -225,19 +225,19 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea
225 switch (mode) {225 switch (mode) {
226 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {226 .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
227 error.Unimplemented => {227 error.Unimplemented => {
228 r.mode = r.mode.toReading();228 r.mode = r.mode.toSimple();
229 return 0;229 return 0;
230 },230 },
231 else => |e| return e,231 else => |e| return e,
232 },232 },
233 .positional_reading => {233 .positional_simple => {
234 const dest = limit.slice(try w.writableSliceGreedy(1));234 const dest = limit.slice(try w.writableSliceGreedy(1));
235 var data: [1][]u8 = .{dest};235 var data: [1][]u8 = .{dest};
236 const n = try readVecPositional(r, &data);236 const n = try readVecPositional(r, &data);
237 w.advance(n);237 w.advance(n);
238 return n;238 return n;
239 },239 },
240 .streaming_reading => {240 .streaming_simple => {
241 const dest = limit.slice(try w.writableSliceGreedy(1));241 const dest = limit.slice(try w.writableSliceGreedy(1));
242 var data: [1][]u8 = .{dest};242 var data: [1][]u8 = .{dest};
243 const n = try readVecStreaming(r, &data);243 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...@@ -251,8 +251,8 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea
251fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {251fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
252 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));252 const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
253 switch (r.mode) {253 switch (r.mode) {
254 .positional, .positional_reading => return readVecPositional(r, data),254 .positional, .positional_simple => return readVecPositional(r, data),
255 .streaming, .streaming_reading => return readVecStreaming(r, data),255 .streaming, .streaming_simple => return readVecStreaming(r, data),
256 .failure => return error.ReadFailed,256 .failure => return error.ReadFailed,
257 }257 }
258}258}
...@@ -320,7 +320,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {...@@ -320,7 +320,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
320 const io = r.io;320 const io = r.io;
321 const file = r.file;321 const file = r.file;
322 switch (r.mode) {322 switch (r.mode) {
323 .positional, .positional_reading => {323 .positional, .positional_simple => {
324 const size = r.getSize() catch {324 const size = r.getSize() catch {
325 r.mode = r.mode.toStreaming();325 r.mode = r.mode.toStreaming();
326 return 0;326 return 0;
...@@ -330,7 +330,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {...@@ -330,7 +330,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
330 setLogicalPos(r, logical_pos + delta);330 setLogicalPos(r, logical_pos + delta);
331 return delta;331 return delta;
332 },332 },
333 .streaming, .streaming_reading => {333 .streaming, .streaming_simple => {
334 // Unfortunately we can't seek forward without knowing the334 // Unfortunately we can't seek forward without knowing the
335 // size because the seek syscalls provided to us will not335 // size because the seek syscalls provided to us will not
336 // return the true end position if a seek would exceed the336 // 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,...@@ -18,172 +18,7 @@ write_file_err: ?WriteFileError = null,
18seek_err: ?SeekError = null,18seek_err: ?SeekError = null,
19interface: Io.Writer,19interface: Io.Writer,
2020
21pub const Mode = union(enum) {21pub const Mode = File.Reader.Mode;
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};
18722
188pub const Error = error{23pub const Error = error{
189 DiskQuota,24 DiskQuota,
...@@ -277,8 +112,7 @@ pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer...@@ -277,8 +112,7 @@ pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer
277 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));112 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
278 switch (w.mode) {113 switch (w.mode) {
279 .positional, .positional_simple => return drainPositional(w, data, splat),114 .positional, .positional_simple => return drainPositional(w, data, splat),
280 .streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat),115 .streaming, .streaming_simple => return drainStreaming(w, data, splat),
281 .terminal_escaped => return drainEscaping(w, data, splat),
282 .failure => return error.WriteFailed,116 .failure => return error.WriteFailed,
283 }117 }
284}118}
...@@ -319,38 +153,13 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer....@@ -319,38 +153,13 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.
319 return w.interface.consume(n);153 return w.interface.consume(n);
320}154}
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
347pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {156pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
348 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));157 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
349 switch (w.mode) {158 switch (w.mode) {
350 .positional => return sendFilePositional(w, file_reader, limit),159 .positional => return sendFilePositional(w, file_reader, limit),
351 .positional_simple => return error.Unimplemented,160 .positional_simple => return error.Unimplemented,
352 .streaming => return sendFileStreaming(w, file_reader, limit),161 .streaming => return sendFileStreaming(w, file_reader, limit),
353 .streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented,162 .streaming_simple => return error.Unimplemented,
354 .failure => return error.WriteFailed,163 .failure => return error.WriteFailed,
355 }164 }
356}165}
...@@ -454,60 +263,7 @@ pub fn end(w: *Writer) EndError!void {...@@ -454,60 +263,7 @@ pub fn end(w: *Writer) EndError!void {
454263
455 .streaming,264 .streaming,
456 .streaming_simple,265 .streaming_simple,
457 .terminal_escaped,
458 .terminal_winapi,
459 .failure,266 .failure,
460 => {},267 => {},
461 }268 }
462}269}
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 = .{...@@ -82,8 +82,8 @@ stderr_writer: File.Writer = .{
82 .io = undefined,82 .io = undefined,
83 .interface = Io.File.Writer.initInterface(&.{}),83 .interface = Io.File.Writer.initInterface(&.{}),
84 .file = if (is_windows) undefined else .stderr(),84 .file = if (is_windows) undefined else .stderr(),
85 .mode = undefined,
86},85},
86stderr_mode: Io.Terminal.Mode = .no_color,
87stderr_writer_initialized: bool = false,87stderr_writer_initialized: bool = false,
8888
89pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {89pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
...@@ -755,9 +755,9 @@ pub fn io(t: *Threaded) Io {...@@ -755,9 +755,9 @@ pub fn io(t: *Threaded) Io {
755755
756 .processExecutableOpen = processExecutableOpen,756 .processExecutableOpen = processExecutableOpen,
757 .processExecutablePath = processExecutablePath,757 .processExecutablePath = processExecutablePath,
758 .lockStderrWriter = lockStderrWriter,758 .lockStderr = lockStderr,
759 .tryLockStderrWriter = tryLockStderrWriter,759 .tryLockStderr = tryLockStderr,
760 .unlockStderrWriter = unlockStderrWriter,760 .unlockStderr = unlockStderr,
761761
762 .now = now,762 .now = now,
763 .sleep = sleep,763 .sleep = sleep,
...@@ -887,9 +887,9 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -887,9 +887,9 @@ pub fn ioBasic(t: *Threaded) Io {
887887
888 .processExecutableOpen = processExecutableOpen,888 .processExecutableOpen = processExecutableOpen,
889 .processExecutablePath = processExecutablePath,889 .processExecutablePath = processExecutablePath,
890 .lockStderrWriter = lockStderrWriter,890 .lockStderr = lockStderr,
891 .tryLockStderrWriter = tryLockStderrWriter,891 .tryLockStderr = tryLockStderr,
892 .unlockStderrWriter = unlockStderrWriter,892 .unlockStderr = unlockStderr,
893893
894 .now = now,894 .now = now,
895 .sleep = sleep,895 .sleep = sleep,
...@@ -10090,47 +10090,70 @@ fn netLookupFallible(...@@ -10090,47 +10090,70 @@ fn netLookupFallible(
10090 return error.OptionUnsupported;10090 return error.OptionUnsupported;
10091}10091}
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 {
10094 const t: *Threaded = @ptrCast(@alignCast(userdata));10098 const t: *Threaded = @ptrCast(@alignCast(userdata));
10095 // Only global mutex since this is Threaded.10099 // Only global mutex since this is Threaded.
10096 Io.stderr_thread_mutex.lock();10100 std.process.stderr_thread_mutex.lock();
10097 if (!t.stderr_writer_initialized) {10101 return initLockedStderr(t, buffer, terminal_mode);
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;
10108}10102}
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 {
10111 const t: *Threaded = @ptrCast(@alignCast(userdata));10109 const t: *Threaded = @ptrCast(@alignCast(userdata));
10112 // Only global mutex since this is Threaded.10110 // 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 {
10114 if (!t.stderr_writer_initialized) {10120 if (!t.stderr_writer_initialized) {
10115 const io_t = ioBasic(t);10121 const io_t = ioBasic(t);
10116 if (is_windows) t.stderr_writer.file = .stderr();10122 if (is_windows) t.stderr_writer.file = .stderr();
10117 t.stderr_writer.io = io_t;10123 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;
10120 t.stderr_writer_initialized = true;10124 t.stderr_writer_initialized = true;
10125 t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file);
10121 }10126 }
10122 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};10127 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) {
10123 t.stderr_writer.interface.flush() catch {};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 };
10124 t.stderr_writer.interface.buffer = buffer;10139 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 };
10126}10144}
1012710145
10128fn unlockStderrWriter(userdata: ?*anyopaque) void {10146fn unlockStderr(userdata: ?*anyopaque) void {
10129 const t: *Threaded = @ptrCast(@alignCast(userdata));10147 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 };
10131 t.stderr_writer.interface.end = 0;10154 t.stderr_writer.interface.end = 0;
10132 t.stderr_writer.interface.buffer = &.{};10155 t.stderr_writer.interface.buffer = &.{};
10133 Io.stderr_thread_mutex.unlock();10156 std.process.stderr_thread_mutex.unlock();
10134}10157}
1013510158
10136pub const PosixAddress = extern union {10159pub 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...@@ -961,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE
961 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {961 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
962 error.EndOfStream => break,962 error.EndOfStream => break,
963 error.Unimplemented => {963 error.Unimplemented => {
964 file_reader.mode = file_reader.mode.toReading();964 file_reader.mode = file_reader.mode.toSimple();
965 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));965 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
966 break;966 break;
967 },967 },
lib/std/Progress.zig+18-18
...@@ -565,10 +565,10 @@ fn updateThreadRun(io: Io) void {...@@ -565,10 +565,10 @@ fn updateThreadRun(io: Io) void {
565 maybeUpdateSize(resize_flag);565 maybeUpdateSize(resize_flag);
566566
567 const buffer, _ = computeRedraw(&serialized_buffer);567 const buffer, _ = computeRedraw(&serialized_buffer);
568 if (io.tryLockStderrWriter(&.{})) |fw| {568 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
569 defer io.unlockStderrWriter();569 defer io.unlockStderr();
570 global_progress.need_clear = true;570 global_progress.need_clear = true;
571 fw.writeAllUnescaped(buffer) catch return;571 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
572 }572 }
573 }573 }
574574
...@@ -576,18 +576,18 @@ fn updateThreadRun(io: Io) void {...@@ -576,18 +576,18 @@ fn updateThreadRun(io: Io) void {
576 const resize_flag = wait(io, global_progress.refresh_rate_ns);576 const resize_flag = wait(io, global_progress.refresh_rate_ns);
577577
578 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {578 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
579 const fw = io.lockStderrWriter(&.{}) catch return;579 const stderr = io.lockStderr(&.{}, null) catch return;
580 defer io.unlockStderrWriter();580 defer io.unlockStderr();
581 return clearWrittenWithEscapeCodes(fw) catch {};581 return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
582 }582 }
583583
584 maybeUpdateSize(resize_flag);584 maybeUpdateSize(resize_flag);
585585
586 const buffer, _ = computeRedraw(&serialized_buffer);586 const buffer, _ = computeRedraw(&serialized_buffer);
587 if (io.tryLockStderrWriter(&.{})) |fw| {587 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
588 defer io.unlockStderrWriter();588 defer io.unlockStderr();
589 global_progress.need_clear = true;589 global_progress.need_clear = true;
590 fw.writeAllUnescaped(buffer) catch return;590 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
591 }591 }
592 }592 }
593}593}
...@@ -609,11 +609,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {...@@ -609,11 +609,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {
609 maybeUpdateSize(resize_flag);609 maybeUpdateSize(resize_flag);
610610
611 const buffer, const nl_n = computeRedraw(&serialized_buffer);611 const buffer, const nl_n = computeRedraw(&serialized_buffer);
612 if (io.tryLockStderrWriter()) |fw| {612 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
613 defer io.unlockStderrWriter();613 defer io.unlockStderr();
614 windowsApiWriteMarker();614 windowsApiWriteMarker();
615 global_progress.need_clear = true;615 global_progress.need_clear = true;
616 fw.writeAllUnescaped(buffer) catch return;616 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
617 windowsApiMoveToMarker(nl_n) catch return;617 windowsApiMoveToMarker(nl_n) catch return;
618 }618 }
619 }619 }
...@@ -622,20 +622,20 @@ fn windowsApiUpdateThreadRun(io: Io) void {...@@ -622,20 +622,20 @@ fn windowsApiUpdateThreadRun(io: Io) void {
622 const resize_flag = wait(io, global_progress.refresh_rate_ns);622 const resize_flag = wait(io, global_progress.refresh_rate_ns);
623623
624 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {624 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
625 _ = io.lockStderrWriter() catch return;625 _ = io.lockStderr(&.{}, null) catch return;
626 defer io.unlockStderrWriter();626 defer io.unlockStderr();
627 return clearWrittenWindowsApi() catch {};627 return clearWrittenWindowsApi() catch {};
628 }628 }
629629
630 maybeUpdateSize(resize_flag);630 maybeUpdateSize(resize_flag);
631631
632 const buffer, const nl_n = computeRedraw(&serialized_buffer);632 const buffer, const nl_n = computeRedraw(&serialized_buffer);
633 if (io.tryLockStderrWriter()) |fw| {633 if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
634 defer io.unlockStderrWriter();634 defer io.unlockStderr();
635 clearWrittenWindowsApi() catch return;635 clearWrittenWindowsApi() catch return;
636 windowsApiWriteMarker();636 windowsApiWriteMarker();
637 global_progress.need_clear = true;637 global_progress.need_clear = true;
638 fw.writeAllUnescaped(buffer) catch return;638 locked_stderr.file_writer.interface.writeAll(buffer) catch return;
639 windowsApiMoveToMarker(nl_n) catch return;639 windowsApiMoveToMarker(nl_n) catch return;
640 }640 }
641 }641 }
...@@ -766,7 +766,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {...@@ -766,7 +766,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
766766
767pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void {767pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void {
768 if (noop_impl or !global_progress.need_clear) return;768 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);
770 global_progress.need_clear = false;770 global_progress.need_clear = false;
771}771}
772772
lib/std/debug.zig+114-118
...@@ -265,29 +265,34 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {...@@ -265,29 +265,34 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
265/// separate from the application's `Io` instance.265/// separate from the application's `Io` instance.
266var static_single_threaded_io: Io.Threaded = .init_single_threaded;266var 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.
269///269///
270/// During the lock, any `std.Progress` information is cleared from the terminal.270/// During the lock, any `std.Progress` information is cleared from the terminal.
271///271///
272/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple272/// The lock is recursive, so it is valid for the same thread to call
273/// times. The primary motivation is that this allows the panic handler to safely dump the stack273/// `lockStderr` multiple times, allowing the panic handler to safely
274/// trace and panic message even if the mutex was held at the panic site.274/// dump the stack trace and panic message even if the mutex was held at the
275/// panic site.
275///276///
276/// The returned `Writer` does not need to be manually flushed: flushing is277/// 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.
278///279///
279/// This is a low-level debugging primitive that bypasses the `Io` interface,280/// This is a low-level debugging primitive that bypasses the `Io` interface,
280/// writing directly to stderr using the most basic syscalls available. This281/// writing directly to stderr using the most basic syscalls available. This
281/// function does not switch threads, switch stacks, or suspend.282/// function does not switch threads, switch stacks, or suspend.
282///283///
283/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with284/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
284/// the application's chosen `Io` implementation.285/// application's chosen `Io` implementation.
285pub fn lockStderrWriter(buffer: []u8) *File.Writer {286pub fn lockStderr(buffer: []u8) Io.Terminal {
286 return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable;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();
287}292}
288293
289pub fn unlockStderrWriter() void {294pub fn unlockStderr() void {
290 static_single_threaded_io.ioBasic().unlockStderrWriter();295 static_single_threaded_io.ioBasic().unlockStderr();
291}296}
292297
293/// Writes to stderr, ignoring errors.298/// Writes to stderr, ignoring errors.
...@@ -299,14 +304,14 @@ pub fn unlockStderrWriter() void {...@@ -299,14 +304,14 @@ pub fn unlockStderrWriter() void {
299/// Uses a 64-byte buffer for formatted printing which is flushed before this304/// Uses a 64-byte buffer for formatted printing which is flushed before this
300/// function returns.305/// function returns.
301///306///
302/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to307/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to
303/// integrate with the application's chosen `Io` implementation.308/// integrate with the application's chosen `Io` implementation.
304pub fn print(comptime fmt: []const u8, args: anytype) void {309pub fn print(comptime fmt: []const u8, args: anytype) void {
305 nosuspend {310 nosuspend {
306 var buffer: [64]u8 = undefined;311 var buffer: [64]u8 = undefined;
307 const stderr = lockStderrWriter(&buffer);312 const stderr = lockStderr(&buffer);
308 defer unlockStderrWriter();313 defer unlockStderr();
309 stderr.interface.print(fmt, stderr.mode.decorateArgs(args)) catch return;314 stderr.writer.print(fmt, args) catch return;
310 }315 }
311}316}
312317
...@@ -322,43 +327,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo {...@@ -322,43 +327,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo {
322/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.327/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
323/// Obtains the stderr mutex while dumping.328/// Obtains the stderr mutex while dumping.
324pub fn dumpHex(bytes: []const u8) void {329pub fn dumpHex(bytes: []const u8) void {
325 const bw, const ttyconf = lockStderrWriter(&.{});330 const stderr = lockStderr(&.{});
326 defer unlockStderrWriter();331 defer unlockStderr();
327 dumpHexFallible(bw, ttyconf, bytes) catch {};332 dumpHexFallible(stderr, bytes) catch {};
328}333}
329334
330/// Prints a hexadecimal view of the bytes, returning any error that occurs.335/// 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;
332 var chunks = mem.window(u8, bytes, 16, 16);338 var chunks = mem.window(u8, bytes, 16, 16);
333 while (chunks.next()) |window| {339 while (chunks.next()) |window| {
334 // 1. Print the address.340 // 1. Print the address.
335 const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;341 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);
337 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.343 // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
338 // Also, make sure all lines are aligned by padding the address.344 // Also, make sure all lines are aligned by padding the address.
339 try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });345 try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
340 try fwm.setColor(bw, .reset);346 try t.setColor(.reset);
341347
342 // 2. Print the bytes.348 // 2. Print the bytes.
343 for (window, 0..) |byte, index| {349 for (window, 0..) |byte, index| {
344 try bw.print("{X:0>2} ", .{byte});350 try w.print("{X:0>2} ", .{byte});
345 if (index == 7) try bw.writeByte(' ');351 if (index == 7) try w.writeByte(' ');
346 }352 }
347 try bw.writeByte(' ');353 try w.writeByte(' ');
348 if (window.len < 16) {354 if (window.len < 16) {
349 var missing_columns = (16 - window.len) * 3;355 var missing_columns = (16 - window.len) * 3;
350 if (window.len < 8) missing_columns += 1;356 if (window.len < 8) missing_columns += 1;
351 try bw.splatByteAll(' ', missing_columns);357 try w.splatByteAll(' ', missing_columns);
352 }358 }
353359
354 // 3. Print the characters.360 // 3. Print the characters.
355 for (window) |byte| {361 for (window) |byte| {
356 if (std.ascii.isPrint(byte)) {362 if (std.ascii.isPrint(byte)) {
357 try bw.writeByte(byte);363 try w.writeByte(byte);
358 } else {364 } else {
359 // Related: https://github.com/ziglang/zig/issues/7600365 // Related: https://github.com/ziglang/zig/issues/7600
360 if (fwm == .terminal_winapi) {366 if (t.mode == .windows_api) {
361 try bw.writeByte('.');367 try w.writeByte('.');
362 continue;368 continue;
363 }369 }
364370
...@@ -366,14 +372,14 @@ pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !v...@@ -366,14 +372,14 @@ pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !v
366 // We don't want to do this for all control codes because most control codes apart from372 // We don't want to do this for all control codes because most control codes apart from
367 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.373 // the ones that Zig has escape sequences for are likely not very useful to print as symbols.
368 switch (byte) {374 switch (byte) {
369 '\n' => try bw.writeAll("␊"),375 '\n' => try w.writeAll("␊"),
370 '\r' => try bw.writeAll("␍"),376 '\r' => try w.writeAll("␍"),
371 '\t' => try bw.writeAll("␉"),377 '\t' => try w.writeAll("␉"),
372 else => try bw.writeByte('.'),378 else => try w.writeByte('.'),
373 }379 }
374 }380 }
375 }381 }
376 try bw.writeByte('\n');382 try w.writeByte('\n');
377 }383 }
378}384}
379385
...@@ -545,26 +551,27 @@ pub fn defaultPanic(...@@ -545,26 +551,27 @@ pub fn defaultPanic(
545 _ = panicking.fetchAdd(1, .seq_cst);551 _ = panicking.fetchAdd(1, .seq_cst);
546552
547 trace: {553 trace: {
548 const stderr = lockStderrWriter(&.{});554 const stderr = lockStderr(&.{});
549 defer unlockStderrWriter();555 defer unlockStderr();
556 const writer = stderr.writer;
550557
551 if (builtin.single_threaded) {558 if (builtin.single_threaded) {
552 stderr.interface.print("panic: ", .{}) catch break :trace;559 writer.print("panic: ", .{}) catch break :trace;
553 } else {560 } else {
554 const current_thread_id = std.Thread.getCurrentId();561 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;
556 }563 }
557 stderr.interface.print("{s}\n", .{msg}) catch break :trace;564 writer.print("{s}\n", .{msg}) catch break :trace;
558565
559 if (@errorReturnTrace()) |t| if (t.index > 0) {566 if (@errorReturnTrace()) |t| if (t.index > 0) {
560 stderr.interface.writeAll("error return context:\n") catch break :trace;567 writer.writeAll("error return context:\n") catch break :trace;
561 writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace;568 writeStackTrace(t, stderr) catch break :trace;
562 stderr.interface.writeAll("\nstack trace:\n") catch break :trace;569 writer.writeAll("\nstack trace:\n") catch break :trace;
563 };570 };
564 writeCurrentStackTrace(.{571 writeCurrentStackTrace(.{
565 .first_address = first_trace_addr orelse @returnAddress(),572 .first_address = first_trace_addr orelse @returnAddress(),
566 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!573 .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;
568 }575 }
569576
570 waitForOtherThreadToFinishPanicking();577 waitForOtherThreadToFinishPanicking();
...@@ -574,8 +581,8 @@ pub fn defaultPanic(...@@ -574,8 +581,8 @@ pub fn defaultPanic(
574 // A panic happened while trying to print a previous panic message.581 // A panic happened while trying to print a previous panic message.
575 // We're still holding the mutex but that's fine as we're going to582 // We're still holding the mutex but that's fine as we're going to
576 // call abort().583 // call abort().
577 const stderr = lockStderrWriter(&.{});584 const stderr = lockStderr(&.{});
578 stderr.interface.writeAll("aborting due to recursive panic\n") catch {};585 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
579 },586 },
580 else => {}, // Panicked while printing the recursive panic message.587 else => {}, // Panicked while printing the recursive panic message.
581 }588 }
...@@ -656,28 +663,29 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:...@@ -656,28 +663,29 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
656/// Write the current stack trace to `writer`, annotated with source locations.663/// Write the current stack trace to `writer`, annotated with source locations.
657///664///
658/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.665/// 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;
660 if (!std.options.allow_stack_tracing) {668 if (!std.options.allow_stack_tracing) {
661 fwm.setColor(writer, .dim) catch {};669 t.setColor(.dim) catch {};
662 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});670 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
663 fwm.setColor(writer, .reset) catch {};671 t.setColor(.reset) catch {};
664 return;672 return;
665 }673 }
666 const di_gpa = getDebugInfoAllocator();674 const di_gpa = getDebugInfoAllocator();
667 const di = getSelfDebugInfo() catch |err| switch (err) {675 const di = getSelfDebugInfo() catch |err| switch (err) {
668 error.UnsupportedTarget => {676 error.UnsupportedTarget => {
669 fwm.setColor(writer, .dim) catch {};677 t.setColor(.dim) catch {};
670 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});678 try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
671 fwm.setColor(writer, .reset) catch {};679 t.setColor(.reset) catch {};
672 return;680 return;
673 },681 },
674 };682 };
675 var it: StackIterator = .init(options.context);683 var it: StackIterator = .init(options.context);
676 defer it.deinit();684 defer it.deinit();
677 if (!it.stratOk(options.allow_unsafe_unwind)) {685 if (!it.stratOk(options.allow_unsafe_unwind)) {
678 fwm.setColor(writer, .dim) catch {};686 t.setColor(.dim) catch {};
679 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});687 try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
680 fwm.setColor(writer, .reset) catch {};688 t.setColor(.reset) catch {};
681 return;689 return;
682 }690 }
683 var total_frames: usize = 0;691 var total_frames: usize = 0;
...@@ -701,31 +709,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -701,31 +709,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
701 error.Unexpected => "unexpected error",709 error.Unexpected => "unexpected error",
702 };710 };
703 if (it.stratOk(options.allow_unsafe_unwind)) {711 if (it.stratOk(options.allow_unsafe_unwind)) {
704 fwm.setColor(writer, .dim) catch {};712 t.setColor(.dim) catch {};
705 try writer.print(713 try writer.print(
706 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",714 "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
707 .{ module_name, unwind_error.address, caption },715 .{ module_name, unwind_error.address, caption },
708 );716 );
709 fwm.setColor(writer, .reset) catch {};717 t.setColor(.reset) catch {};
710 } else {718 } else {
711 fwm.setColor(writer, .dim) catch {};719 t.setColor(.dim) catch {};
712 try writer.print(720 try writer.print(
713 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",721 "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
714 .{ module_name, unwind_error.address, caption },722 .{ module_name, unwind_error.address, caption },
715 );723 );
716 fwm.setColor(writer, .reset) catch {};724 t.setColor(.reset) catch {};
717 return;725 return;
718 }726 }
719 },727 },
720 .end => break,728 .end => break,
721 .frame => |ret_addr| {729 .frame => |ret_addr| {
722 if (total_frames > 10_000) {730 if (total_frames > 10_000) {
723 fwm.setColor(writer, .dim) catch {};731 t.setColor(.dim) catch {};
724 try writer.print(732 try writer.print(
725 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",733 "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
726 .{total_frames},734 .{total_frames},
727 );735 );
728 fwm.setColor(writer, .reset) catch {};736 t.setColor(.reset) catch {};
729 return;737 return;
730 }738 }
731 total_frames += 1;739 total_frames += 1;
...@@ -735,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -735,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
735 }743 }
736 // `ret_addr` is the return address, which is *after* the function call.744 // `ret_addr` is the return address, which is *after* the function call.
737 // Subtract 1 to get an address *in* the function call for a better source location.745 // 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);
739 printed_any_frame = true;747 printed_any_frame = true;
740 },748 },
741 };749 };
...@@ -743,8 +751,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri...@@ -743,8 +751,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
743}751}
744/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.752/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
745pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {753pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
746 const stderr = lockStderrWriter(&.{});754 const stderr = lockStderr(&.{});
747 defer unlockStderrWriter();755 defer unlockStderr();
748 writeCurrentStackTrace(.{756 writeCurrentStackTrace(.{
749 .first_address = a: {757 .first_address = a: {
750 if (options.first_address) |a| break :a a;758 if (options.first_address) |a| break :a a;
...@@ -753,38 +761,28 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {...@@ -753,38 +761,28 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
753 },761 },
754 .context = options.context,762 .context = options.context,
755 .allow_unsafe_unwind = options.allow_unsafe_unwind,763 .allow_unsafe_unwind = options.allow_unsafe_unwind,
756 }, &stderr.interface, stderr.mode) catch |err| switch (err) {764 }, stderr) catch |err| switch (err) {
757 error.WriteFailed => {},765 error.WriteFailed => {},
758 };766 };
759}767}
760768
761pub const FormatStackTrace = struct {769pub const FormatStackTrace = struct {
762 stack_trace: StackTrace,770 stack_trace: StackTrace,
771 terminal_mode: Io.Terminal.Mode = .no_color,
763772
764 pub const Decorated = struct {773 pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void {
765 stack_trace: StackTrace,774 try writer.writeByte('\n');
766 file_writer_mode: File.Writer.Mode,775 try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_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);
779 }776 }
780};777};
781778
782/// Write a previously captured stack trace to `writer`, annotated with source locations.779/// 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;
784 if (!std.options.allow_stack_tracing) {782 if (!std.options.allow_stack_tracing) {
785 fwm.setColor(writer, .dim) catch {};783 t.setColor(.dim) catch {};
786 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});784 try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
787 fwm.setColor(writer, .reset) catch {};785 t.setColor(.reset) catch {};
788 return;786 return;
789 }787 }
790788
...@@ -795,9 +793,9 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer....@@ -795,9 +793,9 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.
795 const di_gpa = getDebugInfoAllocator();793 const di_gpa = getDebugInfoAllocator();
796 const di = getSelfDebugInfo() catch |err| switch (err) {794 const di = getSelfDebugInfo() catch |err| switch (err) {
797 error.UnsupportedTarget => {795 error.UnsupportedTarget => {
798 fwm.setColor(writer, .dim) catch {};796 t.setColor(.dim) catch {};
799 try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});797 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 {};
801 return;799 return;
802 },800 },
803 };801 };
...@@ -806,19 +804,19 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer....@@ -806,19 +804,19 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.
806 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {804 for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
807 // `ret_addr` is the return address, which is *after* the function call.805 // `ret_addr` is the return address, which is *after* the function call.
808 // Subtract 1 to get an address *in* the function call for a better source location.806 // 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);
810 }808 }
811 if (n_frames > captured_frames) {809 if (n_frames > captured_frames) {
812 fwm.setColor(writer, .bold) catch {};810 t.setColor(.bold) catch {};
813 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});811 try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
814 fwm.setColor(writer, .reset) catch {};812 t.setColor(.reset) catch {};
815 }813 }
816}814}
817/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.815/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
818pub fn dumpStackTrace(st: *const StackTrace) void {816pub fn dumpStackTrace(st: *const StackTrace) void {
819 const stderr = lockStderrWriter(&.{});817 const stderr = lockStderr(&.{});
820 defer unlockStderrWriter();818 defer unlockStderr();
821 writeStackTrace(st, &stderr.interface, stderr.mode) catch |err| switch (err) {819 writeStackTrace(st, stderr) catch |err| switch (err) {
822 error.WriteFailed => {},820 error.WriteFailed => {},
823 };821 };
824}822}
...@@ -1117,9 +1115,8 @@ fn printSourceAtAddress(...@@ -1117,9 +1115,8 @@ fn printSourceAtAddress(
1117 gpa: Allocator,1115 gpa: Allocator,
1118 io: Io,1116 io: Io,
1119 debug_info: *SelfInfo,1117 debug_info: *SelfInfo,
1120 writer: *Writer,1118 t: Io.Terminal,
1121 address: usize,1119 address: usize,
1122 fwm: File.Writer.Mode,
1123) Writer.Error!void {1120) Writer.Error!void {
1124 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {1121 const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
1125 error.MissingDebugInfo,1122 error.MissingDebugInfo,
...@@ -1127,40 +1124,39 @@ fn printSourceAtAddress(...@@ -1127,40 +1124,39 @@ fn printSourceAtAddress(
1127 error.InvalidDebugInfo,1124 error.InvalidDebugInfo,
1128 => .unknown,1125 => .unknown,
1129 error.ReadFailed, error.Unexpected, error.Canceled => s: {1126 error.ReadFailed, error.Unexpected, error.Canceled => s: {
1130 fwm.setColor(writer, .dim) catch {};1127 t.setColor(.dim) catch {};
1131 try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});1128 try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
1132 fwm.setColor(writer, .reset) catch {};1129 t.setColor(.reset) catch {};
1133 break :s .unknown;1130 break :s .unknown;
1134 },1131 },
1135 error.OutOfMemory => s: {1132 error.OutOfMemory => s: {
1136 fwm.setColor(writer, .dim) catch {};1133 t.setColor(.dim) catch {};
1137 try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});1134 try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
1138 fwm.setColor(writer, .reset) catch {};1135 t.setColor(.reset) catch {};
1139 break :s .unknown;1136 break :s .unknown;
1140 },1137 },
1141 };1138 };
1142 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);1139 defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
1143 return printLineInfo(1140 return printLineInfo(
1144 io,1141 io,
1145 writer,1142 t,
1146 symbol.source_location,1143 symbol.source_location,
1147 address,1144 address,
1148 symbol.name orelse "???",1145 symbol.name orelse "???",
1149 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",1146 symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
1150 fwm,
1151 );1147 );
1152}1148}
1153fn printLineInfo(1149fn printLineInfo(
1154 io: Io,1150 io: Io,
1155 writer: *Writer,1151 t: Io.Terminal,
1156 source_location: ?SourceLocation,1152 source_location: ?SourceLocation,
1157 address: usize,1153 address: usize,
1158 symbol_name: []const u8,1154 symbol_name: []const u8,
1159 compile_unit_name: []const u8,1155 compile_unit_name: []const u8,
1160 fwm: File.Writer.Mode,
1161) Writer.Error!void {1156) Writer.Error!void {
1162 nosuspend {1157 nosuspend {
1163 fwm.setColor(writer, .bold) catch {};1158 const writer = t.writer;
1159 t.setColor(.bold) catch {};
11641160
1165 if (source_location) |*sl| {1161 if (source_location) |*sl| {
1166 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });1162 try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
...@@ -1168,11 +1164,11 @@ fn printLineInfo(...@@ -1168,11 +1164,11 @@ fn printLineInfo(
1168 try writer.writeAll("???:?:?");1164 try writer.writeAll("???:?:?");
1169 }1165 }
11701166
1171 fwm.setColor(writer, .reset) catch {};1167 t.setColor(.reset) catch {};
1172 try writer.writeAll(": ");1168 try writer.writeAll(": ");
1173 fwm.setColor(writer, .dim) catch {};1169 t.setColor(.dim) catch {};
1174 try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });1170 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 {};
1176 try writer.writeAll("\n");1172 try writer.writeAll("\n");
11771173
1178 // Show the matching source code line if possible1174 // Show the matching source code line if possible
...@@ -1183,9 +1179,9 @@ fn printLineInfo(...@@ -1183,9 +1179,9 @@ fn printLineInfo(
1183 const space_needed = @as(usize, @intCast(sl.column - 1));1179 const space_needed = @as(usize, @intCast(sl.column - 1));
11841180
1185 try writer.splatByteAll(' ', space_needed);1181 try writer.splatByteAll(' ', space_needed);
1186 fwm.setColor(writer, .green) catch {};1182 t.setColor(.green) catch {};
1187 try writer.writeAll("^");1183 try writer.writeAll("^");
1188 fwm.setColor(writer, .reset) catch {};1184 t.setColor(.reset) catch {};
1189 }1185 }
1190 try writer.writeAll("\n");1186 try writer.writeAll("\n");
1191 } else |_| {1187 } else |_| {
...@@ -1554,19 +1550,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex...@@ -1554,19 +1550,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
1554 _ = panicking.fetchAdd(1, .seq_cst);1550 _ = panicking.fetchAdd(1, .seq_cst);
15551551
1556 trace: {1552 trace: {
1557 const stderr = lockStderrWriter(&.{});1553 const stderr = lockStderr(&.{});
1558 defer unlockStderrWriter();1554 defer unlockStderr();
15591555
1560 if (addr) |a| {1556 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;
1562 } else {1558 } 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;
1564 }1560 }
1565 if (opt_ctx) |context| {1561 if (opt_ctx) |context| {
1566 writeCurrentStackTrace(.{1562 writeCurrentStackTrace(.{
1567 .context = context,1563 .context = context,
1568 .allow_unsafe_unwind = true, // we're crashing anyway, give it our all!1564 .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;
1570 }1566 }
1571 }1567 }
1572 },1568 },
...@@ -1575,8 +1571,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex...@@ -1575,8 +1571,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
1575 // A segfault happened while trying to print a previous panic message.1571 // A segfault happened while trying to print a previous panic message.
1576 // We're still holding the mutex but that's fine as we're going to1572 // We're still holding the mutex but that's fine as we're going to
1577 // call abort().1573 // call abort().
1578 const stderr = lockStderrWriter(&.{});1574 const stderr = lockStderr(&.{});
1579 stderr.interface.writeAll("aborting due to recursive panic\n") catch {};1575 stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
1580 },1576 },
1581 else => {}, // Panicked while printing the recursive panic message.1577 else => {}, // Panicked while printing the recursive panic message.
1582 }1578 }
...@@ -1682,21 +1678,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1682,21 +1678,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1682 pub fn dump(t: @This()) void {1678 pub fn dump(t: @This()) void {
1683 if (!enabled) return;1679 if (!enabled) return;
16841680
1685 const stderr = lockStderrWriter(&.{});1681 const stderr = lockStderr(&.{});
1686 defer unlockStderrWriter();1682 defer unlockStderr();
1687 const end = @min(t.index, size);1683 const end = @min(t.index, size);
1688 for (t.addrs[0..end], 0..) |frames_array, i| {1684 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;
1690 var frames_array_mutable = frames_array;1686 var frames_array_mutable = frames_array;
1691 const frames = mem.sliceTo(frames_array_mutable[0..], 0);1687 const frames = mem.sliceTo(frames_array_mutable[0..], 0);
1692 const stack_trace: StackTrace = .{1688 const stack_trace: StackTrace = .{
1693 .index = frames.len,1689 .index = frames.len,
1694 .instruction_addresses = frames,1690 .instruction_addresses = frames,
1695 };1691 };
1696 writeStackTrace(&stack_trace, &stderr.interface, stderr.mode) catch return;1692 writeStackTrace(&stack_trace, stderr) catch return;
1697 }1693 }
1698 if (t.index > end) {1694 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", .{
1700 t.index - end,1696 t.index - end,
1701 }) catch return;1697 }) catch return;
1702 }1698 }
lib/std/log.zig+16-18
...@@ -92,35 +92,33 @@ pub fn defaultLog(...@@ -92,35 +92,33 @@ pub fn defaultLog(
92 args: anytype,92 args: anytype,
93) void {93) void {
94 var buffer: [64]u8 = undefined;94 var buffer: [64]u8 = undefined;
95 const stderr = std.debug.lockStderrWriter(&buffer);95 const stderr = std.debug.lockStderr(&buffer);
96 defer std.debug.unlockStderrWriter();96 defer std.debug.unlockStderr();
97 return defaultLogFileWriter(level, scope, format, args, stderr);97 return defaultLogFileTerminal(level, scope, format, args, stderr) catch {};
98}98}
9999
100pub fn defaultLogFileWriter(100pub fn defaultLogFileTerminal(
101 comptime level: Level,101 comptime level: Level,
102 comptime scope: @EnumLiteral(),102 comptime scope: @EnumLiteral(),
103 comptime format: []const u8,103 comptime format: []const u8,
104 args: anytype,104 args: anytype,
105 fw: *std.Io.File.Writer,105 t: std.Io.Terminal,
106) void {106) std.Io.Writer.Error!void {
107 fw.setColor(switch (level) {107 t.setColor(switch (level) {
108 .err => .red,108 .err => .red,
109 .warn => .yellow,109 .warn => .yellow,
110 .info => .green,110 .info => .green,
111 .debug => .magenta,111 .debug => .magenta,
112 }) catch {};112 }) catch {};
113 fw.setColor(.bold) catch {};113 t.setColor(.bold) catch {};
114 fw.interface.writeAll(level.asText()) catch return;114 try t.writer.writeAll(level.asText());
115 fw.setColor(.reset) catch {};115 t.setColor(.reset) catch {};
116 fw.setColor(.dim) catch {};116 t.setColor(.dim) catch {};
117 fw.setColor(.bold) catch {};117 t.setColor(.bold) catch {};
118 if (scope != .default) {118 if (scope != .default) try t.writer.print("({t})", .{scope});
119 fw.interface.print("({s})", .{@tagName(scope)}) catch return;119 try t.writer.writeAll(": ");
120 }120 t.setColor(.reset) catch {};
121 fw.interface.writeAll(": ") catch return;121 try t.writer.print(format ++ "\n", args);
122 fw.setColor(.reset) catch {};
123 fw.interface.print(format ++ "\n", fw.mode.decorateArgs(args)) catch return;
124}122}
125123
126/// Returns a scoped logging namespace that logs all messages using the scope124/// 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;...@@ -21,6 +21,13 @@ pub const changeCurDirZ = posix.chdirZ;
2121
22pub const GetCwdError = posix.GetCwdError;22pub 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
24/// The result is a slice of `out_buffer`, from index `0`.31/// The result is a slice of `out_buffer`, from index `0`.
25/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).32/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
26/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.33/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.