mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-27 19:09:47 +03:00
std: WIP: debug-level stderr writing
This commit is contained in:
@@ -560,6 +560,13 @@ pub const net = @import("Io/net.zig");
|
||||
userdata: ?*anyopaque,
|
||||
vtable: *const VTable,
|
||||
|
||||
/// This is the global, process-wide protection to coordinate stderr writes.
|
||||
///
|
||||
/// The primary motivation for recursive mutex here is so that a panic while
|
||||
/// stderr mutex is held still dumps the stack trace and other debug
|
||||
/// information.
|
||||
pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
|
||||
|
||||
pub const VTable = struct {
|
||||
/// If it returns `null` it means `result` has been already populated and
|
||||
/// `await` will be a no-op.
|
||||
@@ -733,6 +740,10 @@ pub const VTable = struct {
|
||||
netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface,
|
||||
netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name,
|
||||
netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,
|
||||
|
||||
lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*Writer,
|
||||
tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*Writer,
|
||||
unlockStderrWriter: *const fn (?*anyopaque) void,
|
||||
};
|
||||
|
||||
pub const Cancelable = error{
|
||||
@@ -2167,3 +2178,23 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
/// For doing application-level writes to the standard error stream.
|
||||
/// Coordinates also with debug-level writes that are ignorant of Io interface
|
||||
/// and implementations. When this returns, `stderr_thread_mutex` will be
|
||||
/// locked.
|
||||
///
|
||||
/// See also:
|
||||
/// * `tryLockStderrWriter`
|
||||
pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*Writer {
|
||||
return io.vtable.lockStderrWriter(io.userdata, buffer);
|
||||
}
|
||||
|
||||
/// Same as `lockStderrWriter` but uncancelable and non-blocking.
|
||||
pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*Writer {
|
||||
return io.vtable.tryLockStderrWriter(io.userdata, buffer);
|
||||
}
|
||||
|
||||
pub fn unlockStderrWriter(io: Io) void {
|
||||
return io.vtable.unlockStderrWriter(io.userdata);
|
||||
}
|
||||
|
||||
+3
-2
@@ -342,7 +342,7 @@ pub const Walker = struct {
|
||||
|
||||
/// Recursively iterates over a directory.
|
||||
///
|
||||
/// `dir` must have been opened with `OpenOptions{.iterate = true}`.
|
||||
/// `dir` must have been opened with `OpenOptions.iterate` set to `true`.
|
||||
///
|
||||
/// `Walker.deinit` releases allocated memory and directory handles.
|
||||
///
|
||||
@@ -350,7 +350,8 @@ pub const Walker = struct {
|
||||
///
|
||||
/// `dir` will not be closed after walking it.
|
||||
///
|
||||
/// See also `walkSelectively`.
|
||||
/// See also:
|
||||
/// * `walkSelectively`
|
||||
pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker {
|
||||
return .{ .inner = try walkSelectively(dir, allocator) };
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ use_sendfile: UseSendfile = .default,
|
||||
use_copy_file_range: UseCopyFileRange = .default,
|
||||
use_fcopyfile: UseFcopyfile = .default,
|
||||
|
||||
stderr_writer: Io.Writer,
|
||||
|
||||
pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
|
||||
enabled,
|
||||
disabled,
|
||||
@@ -9514,6 +9516,36 @@ fn netLookupFallible(
|
||||
return error.OptionUnsupported;
|
||||
}
|
||||
|
||||
fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
// Only global mutex since this is Threaded.
|
||||
Io.stderr_thread_mutex.lock();
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
|
||||
t.stderr_writer.flush() catch {};
|
||||
t.stderr_writer.buffer = buffer;
|
||||
return &t.stderr_writer;
|
||||
}
|
||||
|
||||
fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
// Only global mutex since this is Threaded.
|
||||
if (!Io.stderr_thread_mutex.tryLock()) return null;
|
||||
std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {};
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
t.stderr_writer.flush() catch {};
|
||||
t.stderr_writer.buffer = buffer;
|
||||
return &t.stderr_writer;
|
||||
}
|
||||
|
||||
fn unlockStderrWriter(userdata: ?*anyopaque) void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
t.stderr_writer.flush() catch {};
|
||||
t.stderr_writer.end = 0;
|
||||
t.stderr_writer.buffer = &.{};
|
||||
Io.stderr_thread_mutex.unlock();
|
||||
}
|
||||
|
||||
pub const PosixAddress = extern union {
|
||||
any: posix.sockaddr,
|
||||
in: posix.sockaddr.in,
|
||||
|
||||
+51
-98
@@ -13,16 +13,18 @@ const assert = std.debug.assert;
|
||||
const posix = std.posix;
|
||||
const Writer = std.Io.Writer;
|
||||
|
||||
/// `null` if the current node (and its children) should
|
||||
/// not print on update()
|
||||
/// Currently this API only supports this value being set to stderr, which
|
||||
/// happens automatically inside `start`.
|
||||
terminal: Io.File,
|
||||
|
||||
io: Io,
|
||||
|
||||
terminal_mode: TerminalMode,
|
||||
|
||||
update_thread: ?std.Thread,
|
||||
update_worker: ?Io.Future(void),
|
||||
|
||||
/// Atomically set by SIGWINCH as well as the root done() function.
|
||||
redraw_event: std.Thread.ResetEvent,
|
||||
redraw_event: Io.ResetEvent,
|
||||
/// Indicates a request to shut down and reset global state.
|
||||
/// Accessed atomically.
|
||||
done: bool,
|
||||
@@ -95,9 +97,9 @@ pub const Options = struct {
|
||||
/// Must be at least 200 bytes.
|
||||
draw_buffer: []u8 = &default_draw_buffer,
|
||||
/// How many nanoseconds between writing updates to the terminal.
|
||||
refresh_rate_ns: u64 = 80 * std.time.ns_per_ms,
|
||||
refresh_rate_ns: Io.Duration = .fromMilliseconds(80),
|
||||
/// How many nanoseconds to keep the output hidden
|
||||
initial_delay_ns: u64 = 200 * std.time.ns_per_ms,
|
||||
initial_delay_ns: Io.Duration = .fromMilliseconds(200),
|
||||
/// If provided, causes the progress item to have a denominator.
|
||||
/// 0 means unknown.
|
||||
estimated_total_items: usize = 0,
|
||||
@@ -330,7 +332,7 @@ pub const Node = struct {
|
||||
} else {
|
||||
@atomicStore(bool, &global_progress.done, true, .monotonic);
|
||||
global_progress.redraw_event.set();
|
||||
if (global_progress.update_thread) |thread| thread.join();
|
||||
if (global_progress.update_worker) |worker| worker.await(global_progress.io);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,9 +393,10 @@ pub const Node = struct {
|
||||
};
|
||||
|
||||
var global_progress: Progress = .{
|
||||
.io = undefined,
|
||||
.terminal = undefined,
|
||||
.terminal_mode = .off,
|
||||
.update_thread = null,
|
||||
.update_worker = null,
|
||||
.redraw_event = .unset,
|
||||
.refresh_rate_ns = undefined,
|
||||
.initial_delay_ns = undefined,
|
||||
@@ -403,6 +406,7 @@ var global_progress: Progress = .{
|
||||
.done = false,
|
||||
.need_clear = false,
|
||||
.status = .working,
|
||||
.start_failure = .unstarted,
|
||||
|
||||
.node_parents = &node_parents_buffer,
|
||||
.node_storage = &node_storage_buffer,
|
||||
@@ -411,6 +415,13 @@ var global_progress: Progress = .{
|
||||
.node_end_index = 0,
|
||||
};
|
||||
|
||||
pub const StartFailure = union(enum) {
|
||||
unstarted,
|
||||
spawn_ipc_worker: error{ConcurrencyUnavailable},
|
||||
spawn_update_worker: error{ConcurrencyUnavailable},
|
||||
parse_env_var: error{},
|
||||
};
|
||||
|
||||
const node_storage_buffer_len = 83;
|
||||
var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
|
||||
var 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) {
|
||||
/// Asserts there is only one global Progress instance.
|
||||
///
|
||||
/// Call `Node.end` when done.
|
||||
pub fn start(options: Options) Node {
|
||||
pub fn start(options: Options, io: Io) Node {
|
||||
// Ensure there is only 1 global Progress object.
|
||||
if (global_progress.node_end_index != 0) {
|
||||
debug_start_trace.dump();
|
||||
@@ -458,10 +469,10 @@ pub fn start(options: Options) Node {
|
||||
if (noop_impl)
|
||||
return Node.none;
|
||||
|
||||
const io = static_threaded_io.io();
|
||||
global_progress.io = io;
|
||||
|
||||
if (std.process.parseEnvVarInt("ZIG_PROGRESS", u31, 10)) |ipc_fd| {
|
||||
global_progress.update_thread = std.Thread.spawn(.{}, ipcThreadRun, .{
|
||||
global_progress.update_worker = io.concurrent(ipcThreadRun, .{
|
||||
io,
|
||||
@as(Io.File, .{ .handle = switch (@typeInfo(posix.fd_t)) {
|
||||
.int => ipc_fd,
|
||||
@@ -469,7 +480,7 @@ pub fn start(options: Options) Node {
|
||||
else => @compileError("unsupported fd_t of " ++ @typeName(posix.fd_t)),
|
||||
} }),
|
||||
}) catch |err| {
|
||||
std.log.warn("failed to spawn IPC thread for communicating progress to parent: {s}", .{@errorName(err)});
|
||||
global_progress.start_failure = .{ .spawn_ipc_worker = err };
|
||||
return Node.none;
|
||||
};
|
||||
} else |env_err| switch (env_err) {
|
||||
@@ -502,17 +513,17 @@ pub fn start(options: Options) Node {
|
||||
|
||||
if (switch (global_progress.terminal_mode) {
|
||||
.off => unreachable, // handled a few lines above
|
||||
.ansi_escape_codes => std.Thread.spawn(.{}, updateThreadRun, .{io}),
|
||||
.windows_api => if (is_windows) std.Thread.spawn(.{}, windowsApiUpdateThreadRun, .{io}) else unreachable,
|
||||
}) |thread| {
|
||||
global_progress.update_thread = thread;
|
||||
.ansi_escape_codes => io.concurrent(updateThreadRun, .{io}),
|
||||
.windows_api => if (is_windows) io.concurrent(windowsApiUpdateThreadRun, .{io}) else unreachable,
|
||||
}) |future| {
|
||||
global_progress.update_worker = future;
|
||||
} else |err| {
|
||||
std.log.warn("unable to spawn thread for printing progress to terminal: {s}", .{@errorName(err)});
|
||||
global_progress.start_failure = .{ .spawn_update_worker = err };
|
||||
return Node.none;
|
||||
}
|
||||
},
|
||||
else => |e| {
|
||||
std.log.warn("invalid ZIG_PROGRESS file descriptor integer: {s}", .{@errorName(e)});
|
||||
global_progress.start_failure = .{ .parse_env_var = e };
|
||||
return Node.none;
|
||||
},
|
||||
}
|
||||
@@ -545,10 +556,10 @@ fn updateThreadRun(io: Io) void {
|
||||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, _ = computeRedraw(&serialized_buffer);
|
||||
if (stderr_mutex.tryLock()) {
|
||||
defer stderr_mutex.unlock();
|
||||
write(io, buffer) catch return;
|
||||
if (io.tryLockStderrWriter(&.{})) |w| {
|
||||
defer io.unlockStderrWriter();
|
||||
global_progress.need_clear = true;
|
||||
w.writeAll(buffer) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,18 +567,18 @@ fn updateThreadRun(io: Io) void {
|
||||
const resize_flag = wait(global_progress.refresh_rate_ns);
|
||||
|
||||
if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
|
||||
stderr_mutex.lock();
|
||||
defer stderr_mutex.unlock();
|
||||
return clearWrittenWithEscapeCodes(io) catch {};
|
||||
const w = io.lockStderrWriter(&.{}) catch return;
|
||||
defer io.unlockStderrWriter();
|
||||
return clearWrittenWithEscapeCodes(w) catch {};
|
||||
}
|
||||
|
||||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, _ = computeRedraw(&serialized_buffer);
|
||||
if (stderr_mutex.tryLock()) {
|
||||
defer stderr_mutex.unlock();
|
||||
write(io, buffer) catch return;
|
||||
if (io.tryLockStderrWriter(&.{})) |w| {
|
||||
defer io.unlockStderrWriter();
|
||||
global_progress.need_clear = true;
|
||||
w.writeAll(buffer) catch return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -589,11 +600,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {
|
||||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, const nl_n = computeRedraw(&serialized_buffer);
|
||||
if (stderr_mutex.tryLock()) {
|
||||
defer stderr_mutex.unlock();
|
||||
if (io.tryLockStderrWriter()) |w| {
|
||||
defer io.unlockStderrWriter();
|
||||
windowsApiWriteMarker();
|
||||
write(io, buffer) catch return;
|
||||
global_progress.need_clear = true;
|
||||
w.writeAll(buffer) catch return;
|
||||
windowsApiMoveToMarker(nl_n) catch return;
|
||||
}
|
||||
}
|
||||
@@ -602,74 +613,25 @@ fn windowsApiUpdateThreadRun(io: Io) void {
|
||||
const resize_flag = wait(global_progress.refresh_rate_ns);
|
||||
|
||||
if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
|
||||
stderr_mutex.lock();
|
||||
defer stderr_mutex.unlock();
|
||||
_ = io.lockStderrWriter() catch return;
|
||||
defer io.unlockStderrWriter();
|
||||
return clearWrittenWindowsApi() catch {};
|
||||
}
|
||||
|
||||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, const nl_n = computeRedraw(&serialized_buffer);
|
||||
if (stderr_mutex.tryLock()) {
|
||||
defer stderr_mutex.unlock();
|
||||
if (io.tryLockStderrWriter()) |w| {
|
||||
defer io.unlockStderrWriter();
|
||||
clearWrittenWindowsApi() catch return;
|
||||
windowsApiWriteMarker();
|
||||
write(io, buffer) catch return;
|
||||
global_progress.need_clear = true;
|
||||
w.writeAll(buffer) catch return;
|
||||
windowsApiMoveToMarker(nl_n) catch return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
|
||||
///
|
||||
/// During the lock, any `std.Progress` information is cleared from the terminal.
|
||||
///
|
||||
/// The lock is recursive; the same thread may hold the lock multiple times.
|
||||
pub fn lockStdErr() void {
|
||||
const io = stderr_file_writer.io;
|
||||
stderr_mutex.lock();
|
||||
clearWrittenWithEscapeCodes(io) catch {};
|
||||
}
|
||||
|
||||
pub fn unlockStdErr() void {
|
||||
stderr_mutex.unlock();
|
||||
}
|
||||
|
||||
/// Protected by `stderr_mutex`.
|
||||
const stderr_writer: *Writer = &stderr_file_writer.interface;
|
||||
/// Protected by `stderr_mutex`.
|
||||
var stderr_file_writer: Io.File.Writer = .{
|
||||
.io = static_threaded_io.io(),
|
||||
.interface = Io.File.Writer.initInterface(&.{}),
|
||||
.file = if (is_windows) undefined else .stderr(),
|
||||
.mode = .streaming,
|
||||
};
|
||||
var static_threaded_io: Io.Threaded = .init_single_threaded;
|
||||
|
||||
/// Allows the caller to freely write to the returned `Writer`,
|
||||
/// initialized with `buffer`, until `unlockStderrWriter` is called.
|
||||
///
|
||||
/// During the lock, any `std.Progress` information is cleared from the terminal.
|
||||
///
|
||||
/// The lock is recursive; the same thread may hold the lock multiple times.
|
||||
pub fn lockStderrWriter(buffer: []u8) *Io.Writer {
|
||||
const io = stderr_file_writer.io;
|
||||
stderr_mutex.lock();
|
||||
clearWrittenWithEscapeCodes(io) catch {};
|
||||
if (is_windows) stderr_file_writer.file = .stderr();
|
||||
stderr_writer.flush() catch {};
|
||||
stderr_writer.buffer = buffer;
|
||||
return stderr_writer;
|
||||
}
|
||||
|
||||
pub fn unlockStderrWriter() void {
|
||||
stderr_writer.flush() catch {};
|
||||
stderr_writer.end = 0;
|
||||
stderr_writer.buffer = &.{};
|
||||
stderr_mutex.unlock();
|
||||
}
|
||||
|
||||
fn ipcThreadRun(io: Io, file: Io.File) anyerror!void {
|
||||
// Store this data in the thread so that it does not need to be part of the
|
||||
// linker data of the main executable.
|
||||
@@ -793,11 +755,11 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
|
||||
}
|
||||
}
|
||||
|
||||
fn clearWrittenWithEscapeCodes(io: Io) anyerror!void {
|
||||
fn clearWrittenWithEscapeCodes(w: *Io.Writer) anyerror!void {
|
||||
if (noop_impl or !global_progress.need_clear) return;
|
||||
|
||||
try w.writeAll(clear ++ progress_remove);
|
||||
global_progress.need_clear = false;
|
||||
try write(io, clear ++ progress_remove);
|
||||
}
|
||||
|
||||
/// U+25BA or ►
|
||||
@@ -997,7 +959,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
|
||||
const n = posix.read(fd, pipe_buf[bytes_read..]) catch |err| switch (err) {
|
||||
error.WouldBlock => break,
|
||||
else => |e| {
|
||||
std.log.debug("failed to read child progress data: {s}", .{@errorName(e)});
|
||||
std.log.debug("failed to read child progress data: {t}", .{e});
|
||||
main_storage.completed_count = 0;
|
||||
main_storage.estimated_total_count = 0;
|
||||
continue :main_loop;
|
||||
@@ -1424,10 +1386,6 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool {
|
||||
return nl_n + 2 < p.rows;
|
||||
}
|
||||
|
||||
fn write(io: Io, buf: []const u8) anyerror!void {
|
||||
try global_progress.terminal.writeStreamingAll(io, buf);
|
||||
}
|
||||
|
||||
var remaining_write_trash_bytes: usize = 0;
|
||||
|
||||
fn 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
|
||||
error.WouldBlock => return,
|
||||
error.BrokenPipe => return error.BrokenPipe,
|
||||
else => |e| {
|
||||
std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
|
||||
std.log.debug("failed to send progress to parent process: {t}", .{e});
|
||||
return error.BrokenPipe;
|
||||
},
|
||||
}
|
||||
@@ -1476,7 +1434,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi
|
||||
error.WouldBlock => {},
|
||||
error.BrokenPipe => return error.BrokenPipe,
|
||||
else => |e| {
|
||||
std.log.debug("failed to send progress to parent process: {s}", .{@errorName(e)});
|
||||
std.log.debug("failed to send progress to parent process: {t}", .{e});
|
||||
return error.BrokenPipe;
|
||||
},
|
||||
}
|
||||
@@ -1568,11 +1526,6 @@ const have_sigwinch = switch (builtin.os.tag) {
|
||||
else => false,
|
||||
};
|
||||
|
||||
/// The primary motivation for recursive mutex here is so that a panic while
|
||||
/// stderr mutex is held still dumps the stack trace and other debug
|
||||
/// information.
|
||||
var stderr_mutex = std.Thread.Mutex.Recursive.init;
|
||||
|
||||
fn copyAtomicStore(dest: []align(@alignOf(usize)) u8, src: []const u8) void {
|
||||
assert(dest.len == src.len);
|
||||
const chunked_len = dest.len / @sizeOf(usize);
|
||||
|
||||
+50
-23
@@ -262,17 +262,6 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
|
||||
else => true,
|
||||
};
|
||||
|
||||
/// Allows the caller to freely write to stderr until `unlockStdErr` is called.
|
||||
///
|
||||
/// During the lock, any `std.Progress` information is cleared from the terminal.
|
||||
pub fn lockStdErr() void {
|
||||
std.Progress.lockStdErr();
|
||||
}
|
||||
|
||||
pub fn unlockStdErr() void {
|
||||
std.Progress.unlockStdErr();
|
||||
}
|
||||
|
||||
/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
|
||||
///
|
||||
/// During the lock, any `std.Progress` information is cleared from the terminal.
|
||||
@@ -281,17 +270,21 @@ pub fn unlockStdErr() void {
|
||||
/// times. The primary motivation is that this allows the panic handler to safely dump the stack
|
||||
/// trace and panic message even if the mutex was held at the panic site.
|
||||
///
|
||||
/// The returned `Writer` does not need to be manually flushed: flushing is performed automatically
|
||||
/// when the matching `unlockStderrWriter` call occurs.
|
||||
/// The returned `Writer` does not need to be manually flushed: flushing is
|
||||
/// performed automatically when the matching `unlockStderrWriter` call occurs.
|
||||
///
|
||||
/// This is a low-level debugging primitive that bypasses the `Io` interface,
|
||||
/// writing directly to stderr using the most basic syscalls available. This
|
||||
/// function does not switch threads, switch stacks, or suspend.
|
||||
///
|
||||
/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with
|
||||
/// the application's chosen `Io` implementation.
|
||||
pub fn lockStderrWriter(buffer: []u8) struct { *Writer, tty.Config } {
|
||||
const global = struct {
|
||||
var conf: ?tty.Config = null;
|
||||
};
|
||||
Io.stderr_thread_mutex.lock();
|
||||
const w = std.Progress.lockStderrWriter(buffer);
|
||||
const file_writer: *File.Writer = @fieldParentPtr("interface", w);
|
||||
// The stderr lock also locks access to `global.conf`.
|
||||
if (global.conf == null) {
|
||||
global.conf = .detect(file_writer.io, .stderr());
|
||||
if (StderrWriter.singleton.tty_config == null) {
|
||||
StderrWriter.singleton.tty_config = .detect(io, .stderr());
|
||||
}
|
||||
return .{ w, global.conf.? };
|
||||
}
|
||||
@@ -300,11 +293,17 @@ pub fn unlockStderrWriter() void {
|
||||
std.Progress.unlockStderrWriter();
|
||||
}
|
||||
|
||||
/// Print to stderr, silently returning on failure. Intended for use in "printf
|
||||
/// debugging". Use `std.log` functions for proper logging.
|
||||
/// Writes to stderr, ignoring errors.
|
||||
///
|
||||
/// This is a low-level debugging primitive that bypasses the `Io` interface,
|
||||
/// writing directly to stderr using the most basic syscalls available. This
|
||||
/// function does not switch threads, switch stacks, or suspend.
|
||||
///
|
||||
/// Uses a 64-byte buffer for formatted printing which is flushed before this
|
||||
/// function returns.
|
||||
///
|
||||
/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to
|
||||
/// integrate with the application's chosen `Io` implementation.
|
||||
pub fn print(comptime fmt: []const u8, args: anytype) void {
|
||||
var buffer: [64]u8 = undefined;
|
||||
const bw, _ = lockStderrWriter(&buffer);
|
||||
@@ -312,6 +311,34 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
|
||||
nosuspend bw.print(fmt, args) catch return;
|
||||
}
|
||||
|
||||
const StderrWriter = struct {
|
||||
interface: Writer,
|
||||
tty_config: ?tty.Config,
|
||||
|
||||
var singleton: StderrWriter = .{
|
||||
.interface = .{
|
||||
.buffer = &.{},
|
||||
.vtable = &.{ .drain = drain },
|
||||
},
|
||||
.tty_config = null,
|
||||
};
|
||||
|
||||
fn drain(io_w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
|
||||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
var n: usize = 0;
|
||||
const header = w.interface.buffered();
|
||||
if (header.len != 0) n += try std.Io.Threaded.debugWrite(header);
|
||||
for (data[0 .. data.len - 1]) |d| {
|
||||
if (d.len != 0) n += try std.Io.Threaded.debugWrite(d);
|
||||
}
|
||||
const pattern = data[data.len - 1];
|
||||
if (pattern.len != 0) {
|
||||
for (0..splat) |_| n += try std.Io.Threaded.debugWrite(pattern);
|
||||
}
|
||||
return io_w.consume(n);
|
||||
}
|
||||
};
|
||||
|
||||
/// Marked `inline` to propagate a comptime-known error to callers.
|
||||
pub inline fn getSelfDebugInfo() !*SelfInfo {
|
||||
if (SelfInfo == void) return error.UnsupportedTarget;
|
||||
@@ -767,7 +794,7 @@ pub const FormatStackTrace = struct {
|
||||
stack_trace: StackTrace,
|
||||
tty_config: tty.Config,
|
||||
|
||||
pub fn format(context: @This(), writer: *Io.Writer) Io.Writer.Error!void {
|
||||
pub fn format(context: @This(), writer: *Writer) Writer.Error!void {
|
||||
try writer.writeAll("\n");
|
||||
try writeStackTrace(&context.stack_trace, writer, context.tty_config);
|
||||
}
|
||||
@@ -1608,7 +1635,7 @@ test "manage resources correctly" {
|
||||
const gpa = std.testing.allocator;
|
||||
var threaded: Io.Threaded = .init_single_threaded;
|
||||
const io = threaded.ioBasic();
|
||||
var discarding: Io.Writer.Discarding = .init(&.{});
|
||||
var discarding: Writer.Discarding = .init(&.{});
|
||||
var di: SelfInfo = .init;
|
||||
defer di.deinit(gpa);
|
||||
try printSourceAtAddress(
|
||||
|
||||
+14
-2
@@ -80,6 +80,8 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool {
|
||||
return @intFromEnum(level) <= @intFromEnum(std.options.log_level);
|
||||
}
|
||||
|
||||
var static_threaded_io: std.Io.Threaded = .init_single_threaded;
|
||||
|
||||
/// The default implementation for the log function. Custom log functions may
|
||||
/// forward log messages to this function.
|
||||
///
|
||||
@@ -90,10 +92,20 @@ pub fn defaultLog(
|
||||
comptime scope: @EnumLiteral(),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
return defaultLogIo(level, scope, format, args, static_threaded_io.io());
|
||||
}
|
||||
|
||||
pub fn defaultLogIo(
|
||||
comptime level: Level,
|
||||
comptime scope: @EnumLiteral(),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
io: std.Io,
|
||||
) void {
|
||||
var buffer: [64]u8 = undefined;
|
||||
const stderr, const ttyconf = std.debug.lockStderrWriter(&buffer);
|
||||
defer std.debug.unlockStderrWriter();
|
||||
const stderr, const ttyconf = io.lockStderrWriter(&buffer);
|
||||
defer io.unlockStderrWriter();
|
||||
ttyconf.setColor(stderr, switch (level) {
|
||||
.err => .red,
|
||||
.warn => .yellow,
|
||||
|
||||
Reference in New Issue
Block a user