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");...@@ -560,6 +560,13 @@ pub const net = @import("Io/net.zig");
560userdata: ?*anyopaque,560userdata: ?*anyopaque,
561vtable: *const VTable,561vtable: *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
563pub const VTable = struct {570pub const VTable = struct {
564 /// If it returns `null` it means `result` has been already populated and571 /// If it returns `null` it means `result` has been already populated and
565 /// `await` will be a no-op.572 /// `await` will be a no-op.
...@@ -733,6 +740,10 @@ pub const VTable = struct {...@@ -733,6 +740,10 @@ pub const VTable = struct {
733 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,740 netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
734 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,741 netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
735 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,742 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,
736};747};
737748
738pub const Cancelable = error{749pub const Cancelable = error{
...@@ -2167,3 +2178,23 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {...@@ -2167,3 +2178,23 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
2167 else => unreachable,2178 else => unreachable,
2168 }2179 }
2169}2180}
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 {...@@ -342,7 +342,7 @@ pub const Walker = struct {
342342
343/// Recursively iterates over a directory.343/// Recursively iterates over a directory.
344///344///
345/// `dir` must have been opened with `OpenOptions{.iterate = true}`.345/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
346///346///
347/// `Walker.deinit` releases allocated memory and directory handles.347/// `Walker.deinit` releases allocated memory and directory handles.
348///348///
...@@ -350,7 +350,8 @@ pub const Walker = struct {...@@ -350,7 +350,8 @@ pub const Walker = struct {
350///350///
351/// `dir` will not be closed after walking it.351/// `dir` will not be closed after walking it.
352///352///
353/// See also `walkSelectively`.353/// See also:
354/// * `walkSelectively`
354pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {355pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {
355 return .{ .inner = try walkSelectively(dir, allocator) };356 return .{ .inner = try walkSelectively(dir, allocator) };
356}357}
lib/std/Io/Threaded.zig+32
...@@ -77,6 +77,8 @@ use_sendfile: UseSendfile = .default,...@@ -77,6 +77,8 @@ use_sendfile: UseSendfile = .default,
77use_copy_file_range: UseCopyFileRange = .default,77use_copy_file_range: UseCopyFileRange = .default,
78use_fcopyfile: UseFcopyfile = .default,78use_fcopyfile: UseFcopyfile = .default,
7979
80stderr_writer: Io.Writer,
81
80pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {82pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
81 enabled,83 enabled,
82 disabled,84 disabled,
...@@ -9514,6 +9516,36 @@ fn netLookupFallible(...@@ -9514,6 +9516,36 @@ fn netLookupFallible(
9514 return error.OptionUnsupported;9516 return error.OptionUnsupported;
9515}9517}
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
9517pub const PosixAddress = extern union {9549pub const PosixAddress = extern union {
9518 any: posix.sockaddr,9550 any: posix.sockaddr,
9519 in: posix.sockaddr.in,9551 in: posix.sockaddr.in,
lib/std/Progress.zig+51-98
...@@ -13,16 +13,18 @@ const assert = std.debug.assert;...@@ -13,16 +13,18 @@ const assert = std.debug.assert;
13const posix = std.posix;13const posix = std.posix;
14const Writer = std.Io.Writer;14const Writer = std.Io.Writer;
1515
16/// `null` if the current node (and its children) should16/// Currently this API only supports this value being set to stderr, which
17/// not print on update()17/// happens automatically inside `start`.
18terminal: Io.File,18terminal: Io.File,
1919
20io: Io,
21
20terminal_mode: TerminalMode,22terminal_mode: TerminalMode,
2123
22update_thread: ?std.Thread,24update_worker: ?Io.Future(void),
2325
24/// Atomically set by SIGWINCH as well as the root done() function.26/// Atomically set by SIGWINCH as well as the root done() function.
25redraw_event: std.Thread.ResetEvent,27redraw_event: Io.ResetEvent,
26/// Indicates a request to shut down and reset global state.28/// Indicates a request to shut down and reset global state.
27/// Accessed atomically.29/// Accessed atomically.
28done: bool,30done: bool,
...@@ -95,9 +97,9 @@ pub const Options = struct {...@@ -95,9 +97,9 @@ pub const Options = struct {
95 /// Must be at least 200 bytes.97 /// Must be at least 200 bytes.
96 draw_buffer: []u8 = &default_draw_buffer,98 draw_buffer: []u8 = &default_draw_buffer,
97 /// How many nanoseconds between writing updates to the terminal.99 /// 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),
99 /// How many nanoseconds to keep the output hidden101 /// 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),
101 /// If provided, causes the progress item to have a denominator.103 /// If provided, causes the progress item to have a denominator.
102 /// 0 means unknown.104 /// 0 means unknown.
103 estimated_total_items: usize = 0,105 estimated_total_items: usize = 0,
...@@ -330,7 +332,7 @@ pub const Node = struct {...@@ -330,7 +332,7 @@ pub const Node = struct {
330 } else {332 } else {
331 @atomicStore(bool, &global_progress.done, true, .monotonic);333 @atomicStore(bool, &global_progress.done, true, .monotonic);
332 global_progress.redraw_event.set();334 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);
334 }336 }
335 }337 }
336338
...@@ -391,9 +393,10 @@ pub const Node = struct {...@@ -391,9 +393,10 @@ pub const Node = struct {
391};393};
392394
393var global_progress: Progress = .{395var global_progress: Progress = .{
396 .io = undefined,
394 .terminal = undefined,397 .terminal = undefined,
395 .terminal_mode = .off,398 .terminal_mode = .off,
396 .update_thread = null,399 .update_worker = null,
397 .redraw_event = .unset,400 .redraw_event = .unset,
398 .refresh_rate_ns = undefined,401 .refresh_rate_ns = undefined,
399 .initial_delay_ns = undefined,402 .initial_delay_ns = undefined,
...@@ -403,6 +406,7 @@ var global_progress: Progress = .{...@@ -403,6 +406,7 @@ var global_progress: Progress = .{
403 .done = false,406 .done = false,
404 .need_clear = false,407 .need_clear = false,
405 .status = .working,408 .status = .working,
409 .start_failure = .unstarted,
406410
407 .node_parents = &node_parents_buffer,411 .node_parents = &node_parents_buffer,
408 .node_storage = &node_storage_buffer,412 .node_storage = &node_storage_buffer,
...@@ -411,6 +415,13 @@ var global_progress: Progress = .{...@@ -411,6 +415,13 @@ var global_progress: Progress = .{
411 .node_end_index = 0,415 .node_end_index = 0,
412};416};
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
414const node_storage_buffer_len = 83;425const node_storage_buffer_len = 83;
415var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;426var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
416var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;427var 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) {...@@ -437,7 +448,7 @@ const noop_impl = builtin.single_threaded or switch (builtin.os.tag) {
437/// Asserts there is only one global Progress instance.448/// Asserts there is only one global Progress instance.
438///449///
439/// Call `Node.end` when done.450/// Call `Node.end` when done.
440pub fn start(options: Options) Node {451pub fn start(options: Options, io: Io) Node {
441 // Ensure there is only 1 global Progress object.452 // Ensure there is only 1 global Progress object.
442 if (global_progress.node_end_index != 0) {453 if (global_progress.node_end_index != 0) {
443 debug_start_trace.dump();454 debug_start_trace.dump();
...@@ -458,10 +469,10 @@ pub fn start(options: Options) Node {...@@ -458,10 +469,10 @@ pub fn start(options: Options) Node {
458 if (noop_impl)469 if (noop_impl)
459 return Node.none;470 return Node.none;
460471
461 const io = static_threaded_io.io();472 global_progress.io = io;
462473
463 if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {474 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, .{
465 io,476 io,
466 @as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) {477 @as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) {
467 .int => ipc_fd,478 .int => ipc_fd,
...@@ -469,7 +480,7 @@ pub fn start(options: Options) Node {...@@ -469,7 +480,7 @@ pub fn start(options: Options) Node {
469 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),480 else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
470 } }),481 } }),
471 }) catch |err| {482 }) 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 };
473 return Node.none;484 return Node.none;
474 };485 };
475 } else |env_err| switch (env_err) {486 } else |env_err| switch (env_err) {
...@@ -502,17 +513,17 @@ pub fn start(options: Options) Node {...@@ -502,17 +513,17 @@ pub fn start(options: Options) Node {
502513
503 if (switch (global_progress.terminal_mode) {514 if (switch (global_progress.terminal_mode) {
504 .off => unreachable, // handled a few lines above515 .off => unreachable, // handled a few lines above
505 .ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{io}),516 .ansi_escape_codes => io.concurrent(updateThreadRun, .{io}),
506 .windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{io}) else unreachable,517 .windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable,
507 }) |thread| {518 }) |future| {
508 global_progress.update_thread = thread;519 global_progress.update_worker = future;
509 } else |err| {520 } 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 };
511 return Node.none;522 return Node.none;
512 }523 }
513 },524 },
514 else => |e| {525 else => |e| {
515 std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)});526 global_progress.start_failure = .{ .parse_env_var = e };
516 return Node.none;527 return Node.none;
517 },528 },
518 }529 }
...@@ -545,10 +556,10 @@ fn updateThreadRun(io: Io) void {...@@ -545,10 +556,10 @@ fn updateThreadRun(io: Io) void {
545 maybeUpdateSize(resize_flag);556 maybeUpdateSize(resize_flag);
546557
547 const buffer, _ = computeRedraw(&serialized_buffer);558 const buffer, _ = computeRedraw(&serialized_buffer);
548 if (stderr_mutex.tryLock()) {559 if (io.tryLockStderrWriter(&.{})) |w| {
549 defer stderr_mutex.unlock();560 defer io.unlockStderrWriter();
550 write(io, buffer) catch return;
551 global_progress.need_clear = true;561 global_progress.need_clear = true;
562 w.writeAll(buffer) catch return;
552 }563 }
553 }564 }
554565
...@@ -556,18 +567,18 @@ fn updateThreadRun(io: Io) void {...@@ -556,18 +567,18 @@ fn updateThreadRun(io: Io) void {
556 const resize_flag = wait(global_progress.refresh_rate_ns);567 const resize_flag = wait(global_progress.refresh_rate_ns);
557568
558 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {569 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
559 stderr_mutex.lock();570 const w = io.lockStderrWriter(&.{}) catch return;
560 defer stderr_mutex.unlock();571 defer io.unlockStderrWriter();
561 return clearWrittenWithEscapeCodes(io) catch {};572 return clearWrittenWithEscapeCodes(w) catch {};
562 }573 }
563574
564 maybeUpdateSize(resize_flag);575 maybeUpdateSize(resize_flag);
565576
566 const buffer, _ = computeRedraw(&serialized_buffer);577 const buffer, _ = computeRedraw(&serialized_buffer);
567 if (stderr_mutex.tryLock()) {578 if (io.tryLockStderrWriter(&.{})) |w| {
568 defer stderr_mutex.unlock();579 defer io.unlockStderrWriter();
569 write(io, buffer) catch return;
570 global_progress.need_clear = true;580 global_progress.need_clear = true;
581 w.writeAll(buffer) catch return;
571 }582 }
572 }583 }
573}584}
...@@ -589,11 +600,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {...@@ -589,11 +600,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {
589 maybeUpdateSize(resize_flag);600 maybeUpdateSize(resize_flag);
590601
591 const buffer, const nl_n = computeRedraw(&serialized_buffer);602 const buffer, const nl_n = computeRedraw(&serialized_buffer);
592 if (stderr_mutex.tryLock()) {603 if (io.tryLockStderrWriter()) |w| {
593 defer stderr_mutex.unlock();604 defer io.unlockStderrWriter();
594 windowsApiWriteMarker();605 windowsApiWriteMarker();
595 write(io, buffer) catch return;
596 global_progress.need_clear = true;606 global_progress.need_clear = true;
607 w.writeAll(buffer) catch return;
597 windowsApiMoveToMarker(nl_n) catch return;608 windowsApiMoveToMarker(nl_n) catch return;
598 }609 }
599 }610 }
...@@ -602,74 +613,25 @@ fn windowsApiUpdateThreadRun(io: Io) void {...@@ -602,74 +613,25 @@ fn windowsApiUpdateThreadRun(io: Io) void {
602 const resize_flag = wait(global_progress.refresh_rate_ns);613 const resize_flag = wait(global_progress.refresh_rate_ns);
603614
604 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {615 if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
605 stderr_mutex.lock();616 _ = io.lockStderrWriter() catch return;
606 defer stderr_mutex.unlock();617 defer io.unlockStderrWriter();
607 return clearWrittenWindowsApi() catch {};618 return clearWrittenWindowsApi() catch {};
608 }619 }
609620
610 maybeUpdateSize(resize_flag);621 maybeUpdateSize(resize_flag);
611622
612 const buffer, const nl_n = computeRedraw(&serialized_buffer);623 const buffer, const nl_n = computeRedraw(&serialized_buffer);
613 if (stderr_mutex.tryLock()) {624 if (io.tryLockStderrWriter()) |w| {
614 defer stderr_mutex.unlock();625 defer io.unlockStderrWriter();
615 clearWrittenWindowsApi() catch return;626 clearWrittenWindowsApi() catch return;
616 windowsApiWriteMarker();627 windowsApiWriteMarker();
617 write(io, buffer) catch return;
618 global_progress.need_clear = true;628 global_progress.need_clear = true;
629 w.writeAll(buffer) catch return;
619 windowsApiMoveToMarker(nl_n) catch return;630 windowsApiMoveToMarker(nl_n) catch return;
620 }631 }
621 }632 }
622}633}
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
673fn ipcThreadRun(io: Io, file: Io.File) anyerror!void {635fn ipcThreadRun(io: Io, file: Io.File) anyerror!void {
674 // Store this data in the thread so that it does not need to be part of the636 // Store this data in the thread so that it does not need to be part of the
675 // linker data of the main executable.637 // linker data of the main executable.
...@@ -793,11 +755,11 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {...@@ -793,11 +755,11 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
793 }755 }
794}756}
795757
796fn clearWrittenWithEscapeCodes(io: Io) anyerror!void {758fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void {
797 if (noop_impl or !global_progress.need_clear) return;759 if (noop_impl or !global_progress.need_clear) return;
798760
761 try w.writeAll(clear ++ progress_remove);
799 global_progress.need_clear = false;762 global_progress.need_clear = false;
800 try write(io, clear ++ progress_remove);
801}763}
802764
803/// U+25BA or ►765/// U+25BA or ►
...@@ -997,7 +959,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -997,7 +959,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
997 const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) {959 const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) {
998 error.WouldBlock => break,960 error.WouldBlock => break,
999 else => |e| {961 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});
1001 main_storage.completed_count = 0;963 main_storage.completed_count = 0;
1002 main_storage.estimated_total_count = 0;964 main_storage.estimated_total_count = 0;
1003 continue :main_loop;965 continue :main_loop;
...@@ -1424,10 +1386,6 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {...@@ -1424,10 +1386,6 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
1424 return nl_n + 2 < p.rows;1386 return nl_n + 2 < p.rows;
1425}1387}
14261388
1427fn write(io: Io, buf: []const u8) anyerror!void {
1428 try global_progress.terminal.writeStreamingAll(io, buf);
1429}
1430
1431var remaining_write_trash_bytes: usize = 0;1389var remaining_write_trash_bytes: usize = 0;
14321390
1433fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void {1391fn 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...@@ -1459,7 +1417,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi
1459 error.WouldBlock => return,1417 error.WouldBlock => return,
1460 error.BrokenPipe => return error.BrokenPipe,1418 error.BrokenPipe => return error.BrokenPipe,
1461 else => |e| {1419 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});
1463 return error.BrokenPipe;1421 return error.BrokenPipe;
1464 },1422 },
1465 }1423 }
...@@ -1476,7 +1434,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi...@@ -1476,7 +1434,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi
1476 error.WouldBlock => {},1434 error.WouldBlock => {},
1477 error.BrokenPipe => return error.BrokenPipe,1435 error.BrokenPipe => return error.BrokenPipe,
1478 else => |e| {1436 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});
1480 return error.BrokenPipe;1438 return error.BrokenPipe;
1481 },1439 },
1482 }1440 }
...@@ -1568,11 +1526,6 @@ const have_sigwinch = switch (builtin.os.tag) {...@@ -1568,11 +1526,6 @@ const have_sigwinch = switch (builtin.os.tag) {
1568 else => false,1526 else => false,
1569};1527};
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
1576fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void {1529fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void {
1577 assert(dest.len == src.len);1530 assert(dest.len == src.len);
1578 const chunked_len = dest.len / @sizeOf(usize);1531 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) {...@@ -262,17 +262,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
262 else => true,262 else => true,
263};263};
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
276/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.265/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
277///266///
278/// During the lock, any `std.Progress` information is cleared from the terminal.267/// During the lock, any `std.Progress` information is cleared from the terminal.
...@@ -281,17 +270,21 @@ pub fn unlockStdErr() void {...@@ -281,17 +270,21 @@ pub fn unlockStdErr() void {
281/// times. The primary motivation is that this allows the panic handler to safely dump the stack270/// times. The primary motivation is that this allows the panic handler to safely dump the stack
282/// trace and panic message even if the mutex was held at the panic site.271/// trace and panic message even if the mutex was held at the panic site.
283///272///
284/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically273/// The returned `Writer` does not need to be manually flushed: flushing is
285/// when the matching `unlockStderrWriter` call occurs.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.
286pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {282pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {
287 const global = struct {283 Io.stderr_thread_mutex.lock();
288 var conf: ?tty.Config = null;
289 };
290 const w = std.Progress.lockStderrWriter(buffer);284 const w = std.Progress.lockStderrWriter(buffer);
291 const file_writer: *File.Writer = @fieldParentPtr("interface", w);
292 // The stderr lock also locks access to `global.conf`.285 // The stderr lock also locks access to `global.conf`.
293 if (global.conf == null) {286 if (StderrWriter.singleton.tty_config == null) {
294 global.conf = .detect(file_writer.io, .stderr());287 StderrWriter.singleton.tty_config = .detect(io, .stderr());
295 }288 }
296 return .{ w, global.conf.? };289 return .{ w, global.conf.? };
297}290}
...@@ -300,11 +293,17 @@ pub fn unlockStderrWriter() void {...@@ -300,11 +293,17 @@ pub fn unlockStderrWriter() void {
300 std.Progress.unlockStderrWriter();293 std.Progress.unlockStderrWriter();
301}294}
302295
303/// Print to stderr, silently returning on failure. Intended for use in "printf296/// Writes to stderr, ignoring errors.
304/// debugging". Use `std.log` functions for proper logging.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.
305///301///
306/// Uses a 64-byte buffer for formatted printing which is flushed before this302/// Uses a 64-byte buffer for formatted printing which is flushed before this
307/// function returns.303/// function returns.
304///
305/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to
306/// integrate with the application's chosen `Io` implementation.
308pub fn print(comptime fmt: []const u8, args: anytype) void {307pub fn print(comptime fmt: []const u8, args: anytype) void {
309 var buffer: [64]u8 = undefined;308 var buffer: [64]u8 = undefined;
310 const bw, _ = lockStderrWriter(&buffer);309 const bw, _ = lockStderrWriter(&buffer);
...@@ -312,6 +311,34 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {...@@ -312,6 +311,34 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
312 nosuspend bw.print(fmt, args) catch return;311 nosuspend bw.print(fmt, args) catch return;
313}312}
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
315/// Marked `inline` to propagate a comptime-known error to callers.342/// Marked `inline` to propagate a comptime-known error to callers.
316pub inline fn getSelfDebugInfo() !*SelfInfo {343pub inline fn getSelfDebugInfo() !*SelfInfo {
317 if (SelfInfo == void) return error.UnsupportedTarget;344 if (SelfInfo == void) return error.UnsupportedTarget;
...@@ -767,7 +794,7 @@ pub const FormatStackTrace = struct {...@@ -767,7 +794,7 @@ pub const FormatStackTrace = struct {
767 stack_trace: StackTrace,794 stack_trace: StackTrace,
768 tty_config: tty.Config,795 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 {
771 try writer.writeAll("\n");798 try writer.writeAll("\n");
772 try writeStackTrace(&context.stack_trace, writer, context.tty_config);799 try writeStackTrace(&context.stack_trace, writer, context.tty_config);
773 }800 }
...@@ -1608,7 +1635,7 @@ test "manage resources correctly" {...@@ -1608,7 +1635,7 @@ test "manage resources correctly" {
1608 const gpa = std.testing.allocator;1635 const gpa = std.testing.allocator;
1609 var threaded: Io.Threaded = .init_single_threaded;1636 var threaded: Io.Threaded = .init_single_threaded;
1610 const io = threaded.ioBasic();1637 const io = threaded.ioBasic();
1611 var discarding: Io.Writer.Discarding = .init(&.{});1638 var discarding: Writer.Discarding = .init(&.{});
1612 var di: SelfInfo = .init;1639 var di: SelfInfo = .init;
1613 defer di.deinit(gpa);1640 defer di.deinit(gpa);
1614 try printSourceAtAddress(1641 try printSourceAtAddress(
lib/std/log.zig+14-2
...@@ -80,6 +80,8 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {...@@ -80,6 +80,8 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {
80 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);80 return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
81}81}
8282
83var static_threaded_io: std.Io.Threaded = .init_single_threaded;
84
83/// The default implementation for the log function. Custom log functions may85/// The default implementation for the log function. Custom log functions may
84/// forward log messages to this function.86/// forward log messages to this function.
85///87///
...@@ -90,10 +92,20 @@ pub fn defaultLog(...@@ -90,10 +92,20 @@ pub fn defaultLog(
90 comptime scope: @EnumLiteral(),92 comptime scope: @EnumLiteral(),
91 comptime format: []const u8,93 comptime format: []const u8,
92 args: anytype,94 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,
93) void {105) void {
94 var buffer: [64]u8 = undefined;106 var buffer: [64]u8 = undefined;
95 const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer);107 const stderr, const ttyconf = io.lockStderrWriter(&buffer);
96 defer std.debug.unlockStderrWriter();108 defer io.unlockStderrWriter();
97 ttyconf.setColor(stderr, switch (level) {109 ttyconf.setColor(stderr, switch (level) {
98 .err => .red,110 .err => .red,
99 .warn => .yellow,111 .warn => .yellow,