feat: add stack traces to thespian errors in debug builds
This commit is contained in:
parent
3ace316308
commit
f1a89bdf9d
3 changed files with 73 additions and 47 deletions
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue