feat: add stack traces to thespian errors in debug builds

This commit is contained in:
CJ van den Berg 2024-06-26 23:28:17 +02:00
parent 3ace316308
commit f1a89bdf9d
3 changed files with 73 additions and 47 deletions

View file

@ -31,7 +31,7 @@ pub fn write(self: *Self, bytes: []const u8) error{Exit}!usize {
pub fn send(self: *const Self, bytes_: []const u8) tp.result {
if (self.stdin_behavior != .Pipe) return tp.exit("cannot send to closed stdin");
const pid = if (self.pid) |pid| pid else return tp.exit_error(error.Closed);
const pid = if (self.pid) |pid| pid else return tp.exit_error(error.Closed, null);
var bytes = bytes_;
while (bytes.len > 0)
bytes = loop: {
@ -134,11 +134,11 @@ const Proc = struct {
_ = self.args.reset(.free_all);
if (self.child.stdin_behavior == .Pipe)
self.fd_stdin = tp.file_descriptor.init("stdin", self.child.stdin.?.handle) catch |e| return tp.exit_error(e);
self.fd_stdout = tp.file_descriptor.init("stdout", self.child.stdout.?.handle) catch |e| return tp.exit_error(e);
self.fd_stderr = tp.file_descriptor.init("stderr", self.child.stderr.?.handle) catch |e| return tp.exit_error(e);
if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e);
if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e);
self.fd_stdin = tp.file_descriptor.init("stdin", self.child.stdin.?.handle) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.fd_stdout = tp.file_descriptor.init("stdout", self.child.stdout.?.handle) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.fd_stderr = tp.file_descriptor.init("stderr", self.child.stderr.?.handle) catch |e| return tp.exit_error(e, @errorReturnTrace());
if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
tp.receive(&self.receiver);
}
@ -150,22 +150,22 @@ const Proc = struct {
var err_msg: []u8 = "";
if (try m.match(.{ "fd", "stdout", "read_ready" })) {
try self.dispatch_stdout();
if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e);
if (self.fd_stdout) |fd_stdout| fd_stdout.wait_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
} else if (try m.match(.{ "fd", "stderr", "read_ready" })) {
try self.dispatch_stderr();
if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e);
if (self.fd_stderr) |fd_stderr| fd_stderr.wait_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
} else if (try m.match(.{ "fd", "stdin", "write_ready" })) {
if (self.stdin_buffer.items.len > 0) {
if (self.child.stdin) |stdin| {
const written = stdin.write(self.stdin_buffer.items) catch |e| switch (e) {
error.WouldBlock => {
if (self.fd_stdin) |fd_stdin| {
fd_stdin.wait_write() catch |e_| return tp.exit_error(e_);
fd_stdin.wait_write() catch |e_| return tp.exit_error(e_, @errorReturnTrace());
self.write_pending = true;
return;
} else return tp.exit_error(error.WouldBlock);
} else return tp.exit_error(error.WouldBlock, @errorReturnTrace());
},
else => return tp.exit_error(e),
else => return tp.exit_error(e, @errorReturnTrace()),
};
self.write_pending = false;
defer {
@ -178,7 +178,7 @@ const Proc = struct {
std.mem.copyForwards(u8, self.stdin_buffer.items, self.stdin_buffer.items[written..]);
self.stdin_buffer.items.len = self.stdin_buffer.items.len - written;
if (self.fd_stdin) |fd_stdin| {
fd_stdin.wait_write() catch |e| return tp.exit_error(e);
fd_stdin.wait_write() catch |e| return tp.exit_error(e, @errorReturnTrace());
self.write_pending = true;
}
}
@ -186,8 +186,8 @@ const Proc = struct {
}
} else if (try m.match(.{ "stdin", tp.extract(&bytes) })) {
if (self.fd_stdin) |fd_stdin| {
self.stdin_buffer.appendSlice(bytes) catch |e| return tp.exit_error(e);
fd_stdin.wait_write() catch |e| return tp.exit_error(e);
self.stdin_buffer.appendSlice(bytes) catch |e| return tp.exit_error(e, @errorReturnTrace());
fd_stdin.wait_write() catch |e| return tp.exit_error(e, @errorReturnTrace());
self.write_pending = true;
}
} else if (try m.match(.{"stdin_close"})) {
@ -207,7 +207,7 @@ const Proc = struct {
self.child.stderr = null;
}
} else if (try m.match(.{"term"})) {
const term_ = self.child.kill() catch |e| return tp.exit_error(e);
const term_ = self.child.kill() catch |e| return tp.exit_error(e, @errorReturnTrace());
return self.handle_term(term_);
} else if (try m.match(.{ "fd", tp.any, "read_error", tp.extract(&err), tp.extract(&err_msg) })) {
return tp.exit(err_msg);
@ -227,7 +227,7 @@ const Proc = struct {
const stdout = self.child.stdout orelse return tp.exit("cannot read closed stdout");
const bytes = stdout.read(&buffer) catch |e| switch (e) {
error.WouldBlock => return,
else => return tp.exit_error(e),
else => return tp.exit_error(e, @errorReturnTrace()),
};
if (bytes == 0)
return self.handle_terminate();
@ -239,7 +239,7 @@ const Proc = struct {
const stderr = self.child.stderr orelse return tp.exit("cannot read closed stderr");
const bytes = stderr.read(&buffer) catch |e| switch (e) {
error.WouldBlock => return,
else => return tp.exit_error(e),
else => return tp.exit_error(e, @errorReturnTrace()),
};
if (bytes == 0)
return;
@ -247,7 +247,7 @@ const Proc = struct {
}
fn handle_terminate(self: *Proc) tp.result {
return self.handle_term(self.child.wait() catch |e| return tp.exit_error(e));
return self.handle_term(self.child.wait() catch |e| return tp.exit_error(e, @errorReturnTrace()));
}
fn handle_term(self: *Proc, term_: std.process.Child.Term) tp.result {

View file

@ -31,7 +31,7 @@ pub fn write(self: *Self, bytes: []const u8) error{Exit}!usize {
pub fn send(self: *const Self, bytes_: []const u8) tp.result {
if (self.stdin_behavior != .Pipe) return tp.exit("cannot send to closed stdin");
const pid = if (self.pid) |pid| pid else return tp.exit_error(error.Closed);
const pid = if (self.pid) |pid| pid else return tp.exit_error(error.Closed, null);
var bytes = bytes_;
while (bytes.len > 0)
bytes = loop: {
@ -134,11 +134,11 @@ const Proc = struct {
_ = self.args.reset(.free_all);
if (self.child.stdin_behavior == .Pipe)
self.stream_stdin = tp.file_stream.init("stdin", self.child.stdin.?.handle) catch |e| return tp.exit_error(e);
self.stream_stdout = tp.file_stream.init("stdout", self.child.stdout.?.handle) catch |e| return tp.exit_error(e);
self.stream_stderr = tp.file_stream.init("stderr", self.child.stderr.?.handle) catch |e| return tp.exit_error(e);
if (self.stream_stdout) |stream| stream.start_read() catch |e| return tp.exit_error(e);
if (self.stream_stderr) |stream| stream.start_read() catch |e| return tp.exit_error(e);
self.stream_stdin = tp.file_stream.init("stdin", self.child.stdin.?.handle) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.stream_stdout = tp.file_stream.init("stdout", self.child.stdout.?.handle) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.stream_stderr = tp.file_stream.init("stderr", self.child.stderr.?.handle) catch |e| return tp.exit_error(e, @errorReturnTrace());
if (self.stream_stdout) |stream| stream.start_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
if (self.stream_stderr) |stream| stream.start_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
tp.receive(&self.receiver);
}
@ -151,12 +151,12 @@ const Proc = struct {
var err_msg: []u8 = "";
if (try m.match(.{ "stream", "stdout", "read_complete", tp.extract(&bytes) })) {
try self.dispatch_stdout(bytes);
if (self.stream_stdout) |stream| stream.start_read() catch |e| return tp.exit_error(e);
if (self.stream_stdout) |stream| stream.start_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
} else if (try m.match(.{ "stream", "stderr", "read_complete", tp.extract(&bytes) })) {
try self.dispatch_stderr(bytes);
if (self.stream_stderr) |stream| stream.start_read() catch |e| return tp.exit_error(e);
if (self.stream_stderr) |stream| stream.start_read() catch |e| return tp.exit_error(e, @errorReturnTrace());
} else if (try m.match(.{ "stream", "stdin", "write_complete", tp.extract(&bytes_written) })) {
const old_buf = self.stdin_buffer.toOwnedSlice() catch |e| return tp.exit_error(e);
const old_buf = self.stdin_buffer.toOwnedSlice() catch |e| return tp.exit_error(e, @errorReturnTrace());
defer self.stdin_buffer.allocator.free(old_buf);
const bytes_left = old_buf[bytes_written..];
if (bytes_left.len > 0) {
@ -185,7 +185,7 @@ const Proc = struct {
self.child.stderr = null;
}
} else if (try m.match(.{"term"})) {
const term_ = self.child.kill() catch |e| return tp.exit_error(e);
const term_ = self.child.kill() catch |e| return tp.exit_error(e, @errorReturnTrace());
return self.handle_term(term_);
} else if (try m.match(.{ "stream", tp.any, "read_error", tp.extract(&err), tp.extract(&err_msg) })) {
return tp.exit(err_msg);
@ -194,8 +194,8 @@ const Proc = struct {
fn start_write(self: *Proc, bytes: []const u8) tp.result {
if (self.stream_stdin) |stream_stdin| {
self.stdin_buffer.appendSlice(bytes) catch |e| return tp.exit_error(e);
stream_stdin.start_write(self.stdin_buffer.items) catch |e| return tp.exit_error(e);
self.stdin_buffer.appendSlice(bytes) catch |e| return tp.exit_error(e, @errorReturnTrace());
stream_stdin.start_write(self.stdin_buffer.items) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.write_pending = true;
}
}
@ -221,7 +221,7 @@ const Proc = struct {
}
fn handle_terminate(self: *Proc) tp.result {
return self.handle_term(self.child.wait() catch |e| return tp.exit_error(e));
return self.handle_term(self.child.wait() catch |e| return tp.exit_error(e, @errorReturnTrace()));
}
fn handle_term(self: *Proc, term_: std.process.Child.Term) tp.result {

View file

@ -79,7 +79,7 @@ fn Pid(comptime own: Ownership) type {
}
pub fn delay_send(self: Self, a: std.mem.Allocator, delay_us: u64, m: anytype) result {
var h = self.delay_send_cancellable(a, delay_us, m) catch |e| return exit_error(e);
var h = self.delay_send_cancellable(a, delay_us, m) catch |e| return exit_error(e, @errorReturnTrace());
h.deinit();
}
@ -88,10 +88,10 @@ fn Pid(comptime own: Ownership) type {
return Cancellable.init(try DelayedSender.send(self, a, delay_us, msg));
}
pub fn forward_error(self: Self, e: anyerror) result {
pub fn forward_error(self: Self, e: anyerror, stack_trace: ?*std.builtin.StackTrace) result {
return self.send_raw(switch (e) {
error.Exit => .{ .buf = error_message() },
else => exit_message(e),
else => exit_message(e, stack_trace),
});
}
@ -199,32 +199,58 @@ pub const message = struct {
c.cbor_to_json(self.to(c_buffer_type), callback);
}
pub fn match(self: Self, m: anytype) error{Exit}!bool {
return if (cbor.match(self.buf, m)) |ret| ret else |e| exit_error(e);
return if (cbor.match(self.buf, m)) |ret| ret else |e| exit_error(e, @errorReturnTrace());
}
};
pub fn exit_message(e: anytype) message {
return message.fmtbuf(&error_message_buffer, .{ "exit", e }) catch unreachable;
pub fn exit_message(e: anytype, stack_trace: ?*std.builtin.StackTrace) message {
if (stack_trace) |stack_trace_| {
var debug_info_arena_allocator: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer debug_info_arena_allocator.deinit();
const a = debug_info_arena_allocator.allocator();
var out = std.ArrayList(u8).init(a);
store_stack_trace(a, stack_trace_.*, out.writer());
return message.fmtbuf(&error_message_buffer, .{ "exit", e, out.items }) catch unreachable;
} else {
return message.fmtbuf(&error_message_buffer, .{ "exit", e }) catch unreachable;
}
}
fn store_stack_trace(a: std.mem.Allocator, stack_trace: std.builtin.StackTrace, writer: anytype) void {
nosuspend {
if (builtin.strip_debug_info) {
writer.print("Unable to store stack trace: debug info stripped\n", .{}) catch return;
return;
}
const debug_info = std.debug.getSelfDebugInfo() catch |err| {
writer.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
return;
};
std.debug.writeStackTrace(stack_trace, writer, a, debug_info, .no_color) catch |err| {
writer.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
return;
};
}
}
pub fn exit_normal() result {
return set_error_msg(exit_message("normal"));
return set_error_msg(exit_message("normal", null));
}
pub fn exit(e: []const u8) error{Exit} {
return set_error_msg(exit_message(e));
return set_error_msg(exit_message(e, null));
}
pub fn exit_fmt(comptime fmt: anytype, args: anytype) result {
var buf: [1024]u8 = undefined;
const msg = std.fmt.bufPrint(&buf, fmt, args) catch "FMTERROR";
return set_error_msg(exit_message(msg));
return set_error_msg(exit_message(msg, null));
}
pub fn exit_error(e: anyerror) error{Exit} {
pub fn exit_error(e: anyerror, stack_trace: ?*std.builtin.StackTrace) error{Exit} {
return switch (e) {
error.Exit => error.Exit,
else => set_error_msg(exit_message(e)),
else => set_error_msg(exit_message(e, stack_trace)),
};
}
@ -232,14 +258,14 @@ pub fn unexpected(b: message) error{Exit} {
const txt = "UNEXPECTED_MESSAGE: ";
var buf: [max_message_size]u8 = undefined;
@memcpy(buf[0..txt.len], txt);
const json = b.to_json(buf[txt.len..]) catch |e| return exit_error(e);
const json = b.to_json(buf[txt.len..]) catch |e| return exit_error(e, @errorReturnTrace());
return set_error_msg(message.fmt(.{ "exit", buf[0..(txt.len + json.len)] }));
}
pub fn error_message() []const u8 {
if (error_buffer_tl.base) |base|
return base[0..error_buffer_tl.len];
set_error_msg(exit_message("NOERROR")) catch {};
set_error_msg(exit_message("NOERROR", null)) catch {};
return error_message();
}
@ -775,7 +801,7 @@ const CallContext = struct {
fn receive_(self: *Self, _: pid_ref, m: message) result {
defer self.done.set();
self.response.* = m.clone(self.a) catch |e| return exit_error(e);
self.response.* = m.clone(self.a) catch |e| return exit_error(e, @errorReturnTrace());
return exit_normal();
}
};
@ -804,7 +830,7 @@ const DelayedSender = struct {
fn start(self: *DelayedSender) result {
self.receiver = ReceiverT.init(receive_, self);
const m_ = self.message.?;
self.timeout = timeout.init(self.delay_us, m_) catch |e| return exit_error(e);
self.timeout = timeout.init(self.delay_us, m_) catch |e| return exit_error(e, @errorReturnTrace());
self.a.free(m_.buf);
_ = set_trap(true);
receive(&self.receiver);
@ -817,7 +843,7 @@ const DelayedSender = struct {
fn receive_(self: *DelayedSender, _: pid_ref, m_: message) result {
if (try m_.match(.{"CANCEL"})) {
self.timeout.cancel() catch |e| return exit_error(e);
self.timeout.cancel() catch |e| return exit_error(e, @errorReturnTrace());
return;
}
defer self.deinit();