authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-09 18:44:39-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-23 22:15:09-08:00
log78d262d96ee6200c7a6bc0a41fe536d263c24d92
treecfc5dbe215a58d9001f90f1efc1e11d907d669f4
parent03526c59d4e2a00f83347cf06c741a3ed4fec520

std: WIP: debug-level stderr writing


6 files changed, 181 insertions(+), 125 deletions(-)

lib/std/Io.zig+31
......@@ -560,6 +560,13 @@ pub const net = @import("Io/net.zig");
560560userdata: ?*anyopaque,
561561vtable: *const VTable,
562562
563/// This is the global, process-wide protection to coordinate stderr writes.
564///
565/// The primary motivation for recursive mutex here is so that a panic while
566/// stderr mutex is held still dumps the stack trace and other debug
567/// information.
568pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
569
563570pub const VTable = struct {
564571 /// If it returns `null` it means `result` has been already populated and
565572 /// `await` will be a no-op.
......@@ -733,6 +740,10 @@ pub const VTable = struct {
733740 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
734741 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
735742 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,
743
744 lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*Writer,
745 tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*Writer,
746 unlockStderrWriter: *const fn (?*anyopaque) void,
736747};
737748
738749pub const Cancelable = error{
......@@ -2167,3 +2178,23 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
21672178 else => unreachable,
21682179 }
21692180}
2181
2182/// For doing application-level writes to the standard error stream.
2183/// Coordinates also with debug-level writes that are ignorant of Io interface
2184/// and implementations. When this returns, `stderr_thread_mutex` will be
2185/// locked.
2186///
2187/// See also:
2188/// * `tryLockStderrWriter`
2189pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer {
2190 return io.vtable.lockStderrWriter(io.userdata, buffer);
2191}
2192
2193/// Same as `lockStderrWriter` but uncancelable and non-blocking.
2194pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer {
2195 return io.vtable.tryLockStderrWriter(io.userdata, buffer);
2196}
2197
2198pub fn unlockStderrWriter(io: Io) void {
2199 return io.vtable.unlockStderrWriter(io.userdata);
2200}
lib/std/Io/Dir.zig+3-2
......@@ -342,7 +342,7 @@ pub const Walker = struct {
342342
343343/// Recursively iterates over a directory.
344344///
345/// `dir` must have been opened with `OpenOptions{.iterate = true}`.
345/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
346346///
347347/// `Walker.deinit` releases allocated memory and directory handles.
348348///
......@@ -350,7 +350,8 @@ pub const Walker = struct {
350350///
351351/// `dir` will not be closed after walking it.
352352///
353/// See also `walkSelectively`.
353/// See also:
354/// * `walkSelectively`
354355pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {
355356 return .{ .inner = try walkSelectively(dir, allocator) };
356357}
lib/std/Io/Threaded.zig+32
......@@ -77,6 +77,8 @@ use_sendfile: UseSendfile = .default,
7777use_copy_file_range: UseCopyFileRange = .default,
7878use_fcopyfile: UseFcopyfile = .default,
7979
80stderr_writer: Io.Writer,
81
8082pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
8183 enabled,
8284 disabled,
......@@ -9514,6 +9516,36 @@ fn netLookupFallible(
95149516 return error.OptionUnsupported;
95159517}
95169518
9519fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer {
9520 const t: *Threaded = @ptrCast(@alignCast(userdata));
9521 // Only global mutex since this is Threaded.
9522 Io.stderr_thread_mutex.lock();
9523 if (is_windows) t.stderr_writer.file = .stderr();
9524 std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
9525 t.stderr_writer.flush() catch {};
9526 t.stderr_writer.buffer = buffer;
9527 return &t.stderr_writer;
9528}
9529
9530fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer {
9531 const t: *Threaded = @ptrCast(@alignCast(userdata));
9532 // Only global mutex since this is Threaded.
9533 if (!Io.stderr_thread_mutex.tryLock()) return null;
9534 std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {};
9535 if (is_windows) t.stderr_writer.file = .stderr();
9536 t.stderr_writer.flush() catch {};
9537 t.stderr_writer.buffer = buffer;
9538 return &t.stderr_writer;
9539}
9540
9541fn unlockStderrWriter(userdata: ?*anyopaque) void {
9542 const t: *Threaded = @ptrCast(@alignCast(userdata));
9543 t.stderr_writer.flush() catch {};
9544 t.stderr_writer.end = 0;
9545 t.stderr_writer.buffer = &.{};
9546 Io.stderr_thread_mutex.unlock();
9547}
9548
95179549pub const PosixAddress = extern union {
95189550 any: posix.sockaddr,
95199551 in: posix.sockaddr.in,
lib/std/Progress.zig+51-98
......@@ -13,16 +13,18 @@ const assert = std.debug.assert;
1313const posix = std.posix;
1414const Writer = std.Io.Writer;
1515
16/// `null` if the current node (and its children) should
17/// not print on update()
16/// Currently this API only supports this value being set to stderr, which
17/// happens automatically inside `start`.
1818terminal: Io.File,
1919
20io: Io,
21
2022terminal_mode: TerminalMode,
2123
22update_thread: ?std.Thread,
24update_worker: ?Io.Future(void),
2325
2426/// Atomically set by SIGWINCH as well as the root done() function.
25redraw_event: std.Thread.ResetEvent,
27redraw_event: Io.ResetEvent,
2628/// Indicates a request to shut down and reset global state.
2729/// Accessed atomically.
2830done: bool,
......@@ -95,9 +97,9 @@ pub const Options = struct {
9597 /// Must be at least 200 bytes.
9698 draw_buffer: []u8 = &default_draw_buffer,
9799 /// How many nanoseconds between writing updates to the terminal.
98 refresh_rate_ns: u64 = 80 * std.time.ns_per_ms,
100 refresh_rate_ns: Io.Duration = .fromMilliseconds(80),
99101 /// How many nanoseconds to keep the output hidden
100 initial_delay_ns: u64 = 200 * std.time.ns_per_ms,
102 initial_delay_ns: Io.Duration = .fromMilliseconds(200),
101103 /// If provided, causes the progress item to have a denominator.
102104 /// 0 means unknown.
103105 estimated_total_items: usize = 0,
......@@ -330,7 +332,7 @@ pub const Node = struct {
330332 } else {
331333 @atomicStore(bool, &global_progress.done, true, .monotonic);
332334 global_progress.redraw_event.set();
333 if (global_progress.update_thread) |thread| thread.join();
335 if (global_progress.update_worker) |worker| worker.await(global_progress.io);
334336 }
335337 }
336338
......@@ -391,9 +393,10 @@ pub const Node = struct {
391393};
392394
393395var global_progress: Progress = .{
396 .io = undefined,
394397 .terminal = undefined,
395398 .terminal_mode = .off,
396 .update_thread = null,
399 .update_worker = null,
397400 .redraw_event = .unset,
398401 .refresh_rate_ns = undefined,
399402 .initial_delay_ns = undefined,
......@@ -403,6 +406,7 @@ var global_progress: Progress = .{
403406 .done = false,
404407 .need_clear = false,
405408 .status = .working,
409 .start_failure = .unstarted,
406410
407411 .node_parents = &node_parents_buffer,
408412 .node_storage = &node_storage_buffer,
......@@ -411,6 +415,13 @@ var global_progress: Progress = .{
411415 .node_end_index = 0,
412416};
413417
418pub const StartFailure = union(enum) {
419 unstarted,
420 spawn_ipc_worker: error{ConcurrencyUnavailable},
421 spawn_update_worker: error{ConcurrencyUnavailable},
422 parse_env_var: error{},
423};
424
414425const node_storage_buffer_len = 83;
415426var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
416427var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;
......@@ -437,7 +448,7 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) {
437448/// Asserts there is only one global Progress instance.
438449///
439450/// Call `Node.end` when done.
440pub fn start(options: Options) Node {
451pub fn start(options: Options, io: Io) Node {
441452 // Ensure there is only 1 global Progress object.
442453 if (global_progress.node_end_index != 0) {
443454 debug_start_trace.dump();
......@@ -458,10 +469,10 @@ pub fn start(options: Options) Node {
458469 if (noop_impl)
459470 return Node.none;
460471
461 const io = static_threaded_io.io();
472 global_progress.io = io;
462473
463474 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {
464 global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{
475 global_progress.update_worker = io.concurrent(ipcThreadRun, .{
465476 io,
466477 @as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) {
467478 .int => ipc_fd,
......@@ -469,7 +480,7 @@ pub fn start(options: Options) Node {
469480 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
470481 } }),
471482 }) catch |err| {
472 std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)});
483 global_progress.start_failure = .{ .spawn_ipc_worker = err };
473484 return Node.none;
474485 };
475486 } else |env_err| switch (env_err) {
......@@ -502,17 +513,17 @@ pub fn start(options: Options) Node {
502513
503514 if (switch (global_progress.terminal_mode) {
504515 .off => unreachable, // handled a few lines above
505 .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{io}),
506 .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{io}) else unreachable,
507 }) |thread| {
508 global_progress.update_thread = thread;
516 .ansi_escape_codes => io.concurrent(updateThreadRun, .{io}),
517 .windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable,
518 }) |future| {
519 global_progress.update_worker = future;
509520 } else |err| {
510 std.log.warn("unable to spawn thread for printing progress to terminal: {s}", .{@errorName(err)});
521 global_progress.start_failure = .{ .spawn_update_worker = err };
511522 return Node.none;
512523 }
513524 },
514525 else => |e| {
515 std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)});
526 global_progress.start_failure = .{ .parse_env_var = e };
516527 return Node.none;
517528 },
518529 }
......@@ -545,10 +556,10 @@ fn updateThreadRun(io: Io) void {
545556 maybeUpdateSize(resize_flag);
546557
547558 const buffer, _ = computeRedraw(&serialized_buffer);
548 if (stderr_mutex.tryLock()) {
549 defer stderr_mutex.unlock();
550 write(io, buffer) catch return;
559 if (io.tryLockStderrWriter(&.{})) |w| {
560 defer io.unlockStderrWriter();
551561 global_progress.need_clear = true;
562 w.writeAll(buffer) catch return;
552563 }
553564 }
554565
......@@ -556,18 +567,18 @@ fn updateThreadRun(io: Io) void {
556567 const resize_flag = wait(global_progress.refresh_rate_ns);
557568
558569 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
559 stderr_mutex.lock();
560 defer stderr_mutex.unlock();
561 return clearWrittenWithEscapeCodes(io) catch {};
570 const w = io.lockStderrWriter(&.{}) catch return;
571 defer io.unlockStderrWriter();
572 return clearWrittenWithEscapeCodes(w) catch {};
562573 }
563574
564575 maybeUpdateSize(resize_flag);
565576
566577 const buffer, _ = computeRedraw(&serialized_buffer);
567 if (stderr_mutex.tryLock()) {
568 defer stderr_mutex.unlock();
569 write(io, buffer) catch return;
578 if (io.tryLockStderrWriter(&.{})) |w| {
579 defer io.unlockStderrWriter();
570580 global_progress.need_clear = true;
581 w.writeAll(buffer) catch return;
571582 }
572583 }
573584}
......@@ -589,11 +600,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {
589600 maybeUpdateSize(resize_flag);
590601
591602 const buffer, const nl_n = computeRedraw(&serialized_buffer);
592 if (stderr_mutex.tryLock()) {
593 defer stderr_mutex.unlock();
603 if (io.tryLockStderrWriter()) |w| {
604 defer io.unlockStderrWriter();
594605 windowsApiWriteMarker();
595 write(io, buffer) catch return;
596606 global_progress.need_clear = true;
607 w.writeAll(buffer) catch return;
597608 windowsApiMoveToMarker(nl_n) catch return;
598609 }
599610 }
......@@ -602,74 +613,25 @@ fn windowsApiUpdateThreadRun(io: Io) void {
602613 const resize_flag = wait(global_progress.refresh_rate_ns);
603614
604615 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
605 stderr_mutex.lock();
606 defer stderr_mutex.unlock();
616 _ = io.lockStderrWriter() catch return;
617 defer io.unlockStderrWriter();
607618 return clearWrittenWindowsApi() catch {};
608619 }
609620
610621 maybeUpdateSize(resize_flag);
611622
612623 const buffer, const nl_n = computeRedraw(&serialized_buffer);
613 if (stderr_mutex.tryLock()) {
614 defer stderr_mutex.unlock();
624 if (io.tryLockStderrWriter()) |w| {
625 defer io.unlockStderrWriter();
615626 clearWrittenWindowsApi() catch return;
616627 windowsApiWriteMarker();
617 write(io, buffer) catch return;
618628 global_progress.need_clear = true;
629 w.writeAll(buffer) catch return;
619630 windowsApiMoveToMarker(nl_n) catch return;
620631 }
621632 }
622633}
623634
624/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
625///
626/// During the lock, any `std.Progress` information is cleared from the terminal.
627///
628/// The lock is recursive; the same thread may hold the lock multiple times.
629pub fn lockStdErr() void {
630 const io = stderr_file_writer.io;
631 stderr_mutex.lock();
632 clearWrittenWithEscapeCodes(io) catch {};
633}
634
635pub fn unlockStdErr() void {
636 stderr_mutex.unlock();
637}
638
639/// Protected by `stderr_mutex`.
640const stderr_writer: *Writer = &stderr_file_writer.interface;
641/// Protected by `stderr_mutex`.
642var stderr_file_writer: Io.File.Writer = .{
643 .io = static_threaded_io.io(),
644 .interface = Io.File.Writer.initInterface(&.{}),
645 .file = if (is_windows) undefined else .stderr(),
646 .mode = .streaming,
647};
648var static_threaded_io: Io.Threaded = .init_single_threaded;
649
650/// Allows the caller to freely write to the returned `Writer`,
651/// initialized with `buffer`, until `unlockStderrWriter` is called.
652///
653/// During the lock, any `std.Progress` information is cleared from the terminal.
654///
655/// The lock is recursive; the same thread may hold the lock multiple times.
656pub fn lockStderrWriter(buffer: []u8) *Io.Writer {
657 const io = stderr_file_writer.io;
658 stderr_mutex.lock();
659 clearWrittenWithEscapeCodes(io) catch {};
660 if (is_windows) stderr_file_writer.file = .stderr();
661 stderr_writer.flush() catch {};
662 stderr_writer.buffer = buffer;
663 return stderr_writer;
664}
665
666pub fn unlockStderrWriter() void {
667 stderr_writer.flush() catch {};
668 stderr_writer.end = 0;
669 stderr_writer.buffer = &.{};
670 stderr_mutex.unlock();
671}
672
673635fn ipcThreadRun(io: Io, file: Io.File) anyerror!void {
674636 // Store this data in the thread so that it does not need to be part of the
675637 // linker data of the main executable.
......@@ -793,11 +755,11 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
793755 }
794756}
795757
796fn clearWrittenWithEscapeCodes(io: Io) anyerror!void {
758fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void {
797759 if (noop_impl or !global_progress.need_clear) return;
798760
761 try w.writeAll(clear ++ progress_remove);
799762 global_progress.need_clear = false;
800 try write(io, clear ++ progress_remove);
801763}
802764
803765/// U+25BA or ►
......@@ -997,7 +959,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
997959 const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) {
998960 error.WouldBlock => break,
999961 else => |e| {
1000 std.log.debug("failed to read child progress data: {s}", .{@errorName(e)});
962 std.log.debug("failed to read child progress data: {t}", .{e});
1001963 main_storage.completed_count = 0;
1002964 main_storage.estimated_total_count = 0;
1003965 continue :main_loop;
......@@ -1424,10 +1386,6 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
14241386 return nl_n + 2 < p.rows;
14251387}
14261388
1427fn write(io: Io, buf: []const u8) anyerror!void {
1428 try global_progress.terminal.writeStreamingAll(io, buf);
1429}
1430
14311389var remaining_write_trash_bytes: usize = 0;
14321390
14331391fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {
......@@ -1459,7 +1417,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi
14591417 error.WouldBlock => return,
14601418 error.BrokenPipe => return error.BrokenPipe,
14611419 else => |e| {
1462 std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
1420 std.log.debug("failed to send progress to parent process: {t}", .{e});
14631421 return error.BrokenPipe;
14641422 },
14651423 }
......@@ -1476,7 +1434,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi
14761434 error.WouldBlock => {},
14771435 error.BrokenPipe => return error.BrokenPipe,
14781436 else => |e| {
1479 std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
1437 std.log.debug("failed to send progress to parent process: {t}", .{e});
14801438 return error.BrokenPipe;
14811439 },
14821440 }
......@@ -1568,11 +1526,6 @@ const have_sigwinch = switch (builtin.os.tag) {
15681526 else => false,
15691527};
15701528
1571/// The primary motivation for recursive mutex here is so that a panic while
1572/// stderr mutex is held still dumps the stack trace and other debug
1573/// information.
1574var stderr_mutex = std.Thread.Mutex.Recursive.init;
1575
15761529fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void {
15771530 assert(dest.len == src.len);
15781531 const chunked_len = dest.len / @sizeOf(usize);
lib/std/debug.zig+50-23
......@@ -262,17 +262,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
262262 else => true,
263263};
264264
265/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
266///
267/// During the lock, any `std.Progress` information is cleared from the terminal.
268pub fn lockStdErr() void {
269 std.Progress.lockStdErr();
270}
271
272pub fn unlockStdErr() void {
273 std.Progress.unlockStdErr();
274}
275
276265/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
277266///
278267/// During the lock, any `std.Progress` information is cleared from the terminal.
......@@ -281,17 +270,21 @@ pub fn unlockStdErr() void {
281270/// times. The primary motivation is that this allows the panic handler to safely dump the stack
282271/// trace and panic message even if the mutex was held at the panic site.
283272///
284/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically
285/// when the matching `unlockStderrWriter` call occurs.
273/// The returned `Writer` does not need to be manually flushed: flushing is
274/// performed automatically when the matching `unlockStderrWriter` call occurs.
275///
276/// This is a low-level debugging primitive that bypasses the `Io` interface,
277/// writing directly to stderr using the most basic syscalls available. This
278/// function does not switch threads, switch stacks, or suspend.
279///
280/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with
281/// the application's chosen `Io` implementation.
286282pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {
287 const global = struct {
288 var conf: ?tty.Config = null;
289 };
283 Io.stderr_thread_mutex.lock();
290284 const w = std.Progress.lockStderrWriter(buffer);
291 const file_writer: *File.Writer = @fieldParentPtr("interface", w);
292285 // The stderr lock also locks access to `global.conf`.
293 if (global.conf == null) {
294 global.conf = .detect(file_writer.io, .stderr());
286 if (StderrWriter.singleton.tty_config == null) {
287 StderrWriter.singleton.tty_config = .detect(io, .stderr());
295288 }
296289 return .{ w, global.conf.? };
297290}
......@@ -300,11 +293,17 @@ pub fn unlockStderrWriter() void {
300293 std.Progress.unlockStderrWriter();
301294}
302295
303/// Print to stderr, silently returning on failure. Intended for use in "printf
304/// debugging". Use `std.log` functions for proper logging.
296/// Writes to stderr, ignoring errors.
297///
298/// This is a low-level debugging primitive that bypasses the `Io` interface,
299/// writing directly to stderr using the most basic syscalls available. This
300/// function does not switch threads, switch stacks, or suspend.
305301///
306302/// Uses a 64-byte buffer for formatted printing which is flushed before this
307303/// function returns.
304///
305/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to
306/// integrate with the application's chosen `Io` implementation.
308307pub fn print(comptime fmt: []const u8, args: anytype) void {
309308 var buffer: [64]u8 = undefined;
310309 const bw, _ = lockStderrWriter(&buffer);
......@@ -312,6 +311,34 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
312311 nosuspend bw.print(fmt, args) catch return;
313312}
314313
314const StderrWriter = struct {
315 interface: Writer,
316 tty_config: ?tty.Config,
317
318 var singleton: StderrWriter = .{
319 .interface = .{
320 .buffer = &.{},
321 .vtable = &.{ .drain = drain },
322 },
323 .tty_config = null,
324 };
325
326 fn drain(io_w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
327 const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
328 var n: usize = 0;
329 const header = w.interface.buffered();
330 if (header.len != 0) n += try std.Io.Threaded.debugWrite(header);
331 for (data[0 .. data.len - 1]) |d| {
332 if (d.len != 0) n += try std.Io.Threaded.debugWrite(d);
333 }
334 const pattern = data[data.len - 1];
335 if (pattern.len != 0) {
336 for (0..splat) |_| n += try std.Io.Threaded.debugWrite(pattern);
337 }
338 return io_w.consume(n);
339 }
340};
341
315342/// Marked `inline` to propagate a comptime-known error to callers.
316343pub inline fn getSelfDebugInfo() !*SelfInfo {
317344 if (SelfInfo == void) return error.UnsupportedTarget;
......@@ -767,7 +794,7 @@ pub const FormatStackTrace = struct {
767794 stack_trace: StackTrace,
768795 tty_config: tty.Config,
769796
770 pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void {
797 pub fn format(context: @This(), writer: *Writer) Writer.Error!void {
771798 try writer.writeAll("\n");
772799 try writeStackTrace(&context.stack_trace, writer, context.tty_config);
773800 }
......@@ -1608,7 +1635,7 @@ test "manage resources correctly" {
16081635 const gpa = std.testing.allocator;
16091636 var threaded: Io.Threaded = .init_single_threaded;
16101637 const io = threaded.ioBasic();
1611 var discarding: Io.Writer.Discarding = .init(&.{});
1638 var discarding: Writer.Discarding = .init(&.{});
16121639 var di: SelfInfo = .init;
16131640 defer di.deinit(gpa);
16141641 try printSourceAtAddress(
lib/std/log.zig+14-2
......@@ -80,6 +80,8 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {
8080 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
8181}
8282
83var static_threaded_io: std.Io.Threaded = .init_single_threaded;
84
8385/// The default implementation for the log function. Custom log functions may
8486/// forward log messages to this function.
8587///
......@@ -90,10 +92,20 @@ pub fn defaultLog(
9092 comptime scope: @EnumLiteral(),
9193 comptime format: []const u8,
9294 args: anytype,
95) void {
96 return defaultLogIo(level, scope, format, args, static_threaded_io.io());
97}
98
99pub fn defaultLogIo(
100 comptime level: Level,
101 comptime scope: @EnumLiteral(),
102 comptime format: []const u8,
103 args: anytype,
104 io: std.Io,
93105) void {
94106 var buffer: [64]u8 = undefined;
95 const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer);
96 defer std.debug.unlockStderrWriter();
107 const stderr, const ttyconf = io.lockStderrWriter(&buffer);
108 defer io.unlockStderrWriter();
97109 ttyconf.setColor(stderr, switch (level) {
98110 .err => .red,
99111 .warn => .yellow,