mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
562 lines
20 KiB
Zig
562 lines
20 KiB
Zig
const std = @import("std");
|
|
const logger = @import("bun").logger;
|
|
const bun = @import("bun");
|
|
const string = bun.string;
|
|
const Output = bun.Output;
|
|
const Global = bun.Global;
|
|
const Environment = bun.Environment;
|
|
const strings = bun.strings;
|
|
const MutableString = bun.MutableString;
|
|
const stringZ = bun.stringZ;
|
|
const default_allocator = bun.default_allocator;
|
|
const C = bun.C;
|
|
const CLI = @import("./cli.zig").Cli;
|
|
const Features = @import("./analytics/analytics_thread.zig").Features;
|
|
const Platform = @import("./analytics/analytics_thread.zig").GenerateHeader.GeneratePlatform;
|
|
const HTTP = @import("bun").HTTP.AsyncHTTP;
|
|
const CrashReporter = @import("./crash_reporter.zig");
|
|
|
|
const Report = @This();
|
|
|
|
var crash_report_writer: CrashReportWriter = CrashReportWriter{ .file = null };
|
|
const CrashWritable = if (Environment.isWasm) std.io.fixedBufferStream([2048]u8) else std.fs.File.Writer;
|
|
var crash_reporter_path: [1024]u8 = undefined;
|
|
pub const CrashReportWriter = struct {
|
|
file: ?std.io.BufferedWriter(4096, CrashWritable) = null,
|
|
file_path: []const u8 = "",
|
|
|
|
pub fn printFrame(_: ?*anyopaque, frame: CrashReporter.StackFrame) void {
|
|
const function_name = if (frame.function_name.len > 0) frame.function_name else "[function ?]";
|
|
const filename = if (frame.filename.len > 0) frame.function_name else "[file ?]";
|
|
crash_report_writer.print("[0x{X}] - <b>{s}<r> {s}:{d}\n", .{ frame.pc, function_name, filename, frame.line_number });
|
|
}
|
|
|
|
pub fn dump() void {
|
|
if (comptime !Environment.isWasm)
|
|
CrashReporter.print();
|
|
}
|
|
|
|
pub fn done() void {
|
|
if (comptime !Environment.isWasm)
|
|
CrashReporter.print();
|
|
}
|
|
|
|
pub fn print(this: *CrashReportWriter, comptime fmt: string, args: anytype) void {
|
|
Output.prettyError(fmt, args);
|
|
if (this.file) |*file| {
|
|
var writer = file.writer();
|
|
writer.print(comptime Output.prettyFmt(fmt, false), args) catch {};
|
|
}
|
|
}
|
|
|
|
pub fn flush(this: *CrashReportWriter) void {
|
|
if (this.file) |*file| {
|
|
file.flush() catch {};
|
|
}
|
|
Output.flush();
|
|
}
|
|
|
|
pub fn generateFile(this: *CrashReportWriter) void {
|
|
if (this.file != null) return;
|
|
|
|
var base_dir: []const u8 = ".";
|
|
if (std.os.getenv("BUN_INSTALL")) |install_dir| {
|
|
base_dir = std.mem.trimRight(u8, install_dir, std.fs.path.sep_str);
|
|
} else if (std.os.getenv("HOME")) |home_dir| {
|
|
base_dir = std.mem.trimRight(u8, home_dir, std.fs.path.sep_str);
|
|
}
|
|
const file_path = std.fmt.bufPrintZ(
|
|
&crash_reporter_path,
|
|
"{s}/.bun-crash/v{s}-{d}.crash",
|
|
.{ base_dir, Global.package_json_version, @intCast(u64, @maximum(std.time.milliTimestamp(), 0)) },
|
|
) catch return;
|
|
|
|
std.fs.cwd().makeDir(std.fs.path.dirname(std.mem.span(file_path)).?) catch {};
|
|
var file = std.fs.cwd().createFileZ(file_path, .{ .truncate = true }) catch return;
|
|
this.file = std.io.bufferedWriter(
|
|
file.writer(),
|
|
);
|
|
this.file_path = std.mem.span(file_path);
|
|
}
|
|
|
|
pub fn printPath(this: *CrashReportWriter) void {
|
|
var display_path = this.file_path;
|
|
|
|
if (this.file_path.len > 0) {
|
|
var tilda = false;
|
|
if (std.os.getenv("HOME")) |home_dir| {
|
|
if (strings.hasPrefix(display_path, home_dir)) {
|
|
display_path = display_path[home_dir.len..];
|
|
tilda = true;
|
|
}
|
|
}
|
|
|
|
if (tilda) {
|
|
Output.prettyError("\nCrash report saved to:\n <b>~{s}<r>\n", .{display_path});
|
|
} else {
|
|
Output.prettyError("\nCrash report saved to:\n <b>{s}<r>\n", .{display_path});
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn printMetadata() void {
|
|
@setCold(true);
|
|
crash_report_writer.generateFile();
|
|
|
|
const cmd_label: string = if (CLI.cmd) |tag| @tagName(tag) else "Unknown";
|
|
|
|
const platform = if (Environment.isMac) "macOS" else "Linux";
|
|
const arch = if (Environment.isAarch64)
|
|
if (Environment.isMac) "Silicon" else "arm64"
|
|
else
|
|
"x64";
|
|
|
|
var analytics_platform = Platform.forOS();
|
|
|
|
crash_report_writer.print(
|
|
\\
|
|
\\<r>----- bun meta -----
|
|
++ "\nBun v" ++ Global.package_json_version_with_sha ++ " " ++ platform ++ " " ++ arch ++ " {s}\n" ++
|
|
\\{s}: {}
|
|
\\
|
|
, .{
|
|
analytics_platform.version,
|
|
cmd_label,
|
|
Features.formatter(),
|
|
});
|
|
|
|
const http_count = HTTP.active_requests_count.loadUnchecked();
|
|
if (http_count > 0)
|
|
crash_report_writer.print(
|
|
\\HTTP: {d}
|
|
\\
|
|
, .{http_count});
|
|
|
|
if (comptime bun.use_mimalloc) {
|
|
var elapsed_msecs: usize = 0;
|
|
var user_msecs: usize = 0;
|
|
var system_msecs: usize = 0;
|
|
var current_rss: usize = 0;
|
|
var peak_rss: usize = 0;
|
|
var current_commit: usize = 0;
|
|
var peak_commit: usize = 0;
|
|
var page_faults: usize = 0;
|
|
const mimalloc = @import("allocators/mimalloc.zig");
|
|
mimalloc.mi_process_info(
|
|
&elapsed_msecs,
|
|
&user_msecs,
|
|
&system_msecs,
|
|
¤t_rss,
|
|
&peak_rss,
|
|
¤t_commit,
|
|
&peak_commit,
|
|
&page_faults,
|
|
);
|
|
crash_report_writer.print("Elapsed: {d}ms | User: {d}ms | Sys: {d}ms\nRSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{
|
|
elapsed_msecs,
|
|
user_msecs,
|
|
system_msecs,
|
|
std.fmt.fmtIntSizeDec(current_rss),
|
|
std.fmt.fmtIntSizeDec(peak_rss),
|
|
std.fmt.fmtIntSizeDec(current_commit),
|
|
page_faults,
|
|
});
|
|
}
|
|
|
|
crash_report_writer.print("----- bun meta -----\n", .{});
|
|
}
|
|
var has_printed_fatal = false;
|
|
var has_printed_crash = false;
|
|
pub fn fatal(err_: ?anyerror, msg_: ?string) void {
|
|
const had_printed_fatal = has_printed_fatal;
|
|
if (!has_printed_fatal) {
|
|
has_printed_fatal = true;
|
|
crash_report_writer.generateFile();
|
|
|
|
if (err_) |err| {
|
|
if (Output.isEmojiEnabled()) {
|
|
crash_report_writer.print(
|
|
"\n<r><red>error<r><d>:<r> <b>{s}<r>\n",
|
|
.{@errorName(err)},
|
|
);
|
|
} else {
|
|
crash_report_writer.print(
|
|
"\n<r>error: {s}\n\n",
|
|
.{@errorName(err)},
|
|
);
|
|
}
|
|
}
|
|
|
|
if (msg_) |msg| {
|
|
const msg_ptr = @ptrToInt(msg.ptr);
|
|
if (msg_ptr > 0) {
|
|
const len = @maximum(@minimum(msg.len, 1024), 0);
|
|
|
|
if (len > 0) {
|
|
if (Output.isEmojiEnabled()) {
|
|
crash_report_writer.print(
|
|
"\n<r><red>uh-oh<r><d>:<r> <b>{s}<r>\n",
|
|
.{msg[0..len]},
|
|
);
|
|
} else {
|
|
crash_report_writer.print(
|
|
"\n<r>Panic: {s}\n\n",
|
|
.{msg[0..len]},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (err_ == null) {
|
|
if (Output.isEmojiEnabled()) {
|
|
if (msg_ == null and err_ == null) {
|
|
crash_report_writer.print("<r><red>", .{});
|
|
} else {
|
|
crash_report_writer.print("<r>", .{});
|
|
}
|
|
crash_report_writer.print("bun will crash now<r> 😭😭😭\n", .{});
|
|
} else {
|
|
crash_report_writer.print("bun has crashed :'(\n", .{});
|
|
}
|
|
}
|
|
crash_report_writer.flush();
|
|
|
|
printMetadata();
|
|
|
|
crash_report_writer.flush();
|
|
|
|
// It only is a real crash report if it's not coming from Zig
|
|
|
|
if (comptime !@import("bun").JSC.is_bindgen) {
|
|
std.mem.doNotOptimizeAway(&Bun__crashReportWrite);
|
|
Bun__crashReportDumpStackTrace(&crash_report_writer);
|
|
}
|
|
|
|
crash_report_writer.flush();
|
|
|
|
crash_report_writer.printPath();
|
|
}
|
|
|
|
if (!had_printed_fatal) {
|
|
crash_report_writer.print("\nSearch GitHub issues https://bun.sh/issues or ask for #help in https://bun.sh/discord\n\n", .{});
|
|
crash_report_writer.flush();
|
|
}
|
|
}
|
|
|
|
var globalError_ranOnce = false;
|
|
|
|
export fn Bun__crashReportWrite(ctx: *CrashReportWriter, bytes_ptr: [*]const u8, len: usize) void {
|
|
if (len > 0)
|
|
ctx.print("{s}\n", .{bytes_ptr[0..len]});
|
|
}
|
|
|
|
extern "C" fn Bun__crashReportDumpStackTrace(ctx: *anyopaque) void;
|
|
|
|
pub noinline fn handleCrash(signal: i32, addr: usize) void {
|
|
const had_printed_fatal = has_printed_fatal;
|
|
if (has_printed_fatal) return;
|
|
has_printed_fatal = true;
|
|
|
|
crash_report_writer.generateFile();
|
|
|
|
const name = switch (signal) {
|
|
std.os.SIG.SEGV => error.SegmentationFault,
|
|
std.os.SIG.ILL => error.InstructionError,
|
|
std.os.SIG.BUS => error.BusError,
|
|
else => error.Crash,
|
|
};
|
|
|
|
crash_report_writer.print(
|
|
"\n<r><red>{s}<d> at 0x{any}\n\n",
|
|
.{ @errorName(name), std.fmt.fmtSliceHexUpper(std.mem.asBytes(&addr)) },
|
|
);
|
|
printMetadata();
|
|
if (comptime !@import("bun").JSC.is_bindgen) {
|
|
std.mem.doNotOptimizeAway(&Bun__crashReportWrite);
|
|
Bun__crashReportDumpStackTrace(&crash_report_writer);
|
|
}
|
|
|
|
if (!had_printed_fatal) {
|
|
crash_report_writer.print("\nAsk for #help in https://bun.sh/discord or go to https://bun.sh/issues\n\n", .{});
|
|
}
|
|
crash_report_writer.flush();
|
|
|
|
if (crash_report_writer.file) |file| {
|
|
// don't handle return codes here
|
|
_ = std.os.system.close(file.unbuffered_writer.context.handle);
|
|
}
|
|
|
|
crash_report_writer.file = null;
|
|
if (comptime Environment.isDebug) {
|
|
if (@errorReturnTrace()) |stack| {
|
|
std.debug.dumpStackTrace(stack.*);
|
|
}
|
|
}
|
|
|
|
std.c._exit(128 + @truncate(u8, @intCast(u8, @maximum(signal, 0))));
|
|
}
|
|
|
|
pub noinline fn globalError(err: anyerror) noreturn {
|
|
@setCold(true);
|
|
|
|
if (@atomicRmw(bool, &globalError_ranOnce, .Xchg, true, .Monotonic)) {
|
|
Global.exit(1);
|
|
}
|
|
|
|
switch (err) {
|
|
error.SyntaxError => {
|
|
Output.prettyError(
|
|
"\n<r><red>SyntaxError<r><d>:<r> An error occurred while parsing code",
|
|
.{},
|
|
);
|
|
Global.exit(1);
|
|
},
|
|
error.OutOfMemory => {
|
|
Output.prettyError(
|
|
"\n<r><red>OutOfMemory<r><d>:<r> There might be an infinite loop somewhere",
|
|
.{},
|
|
);
|
|
printMetadata();
|
|
Global.exit(1);
|
|
},
|
|
error.CurrentWorkingDirectoryUnlinked => {
|
|
Output.prettyError(
|
|
"\n<r><red>error: <r>The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again.",
|
|
.{},
|
|
);
|
|
Global.exit(1);
|
|
},
|
|
error.BundleFailed => {
|
|
Output.prettyError(
|
|
"\n<r><red>BundleFailed<r>",
|
|
.{},
|
|
);
|
|
Global.exit(1);
|
|
},
|
|
error.InvalidArgument, error.InstallFailed => {
|
|
Global.exit(1);
|
|
},
|
|
error.SystemFdQuotaExceeded => {
|
|
const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit);
|
|
if (comptime Environment.isMac) {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
\\That will only work until you reboot.
|
|
\\
|
|
,
|
|
.{
|
|
limit.cur,
|
|
},
|
|
);
|
|
} else {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf<r>
|
|
\\ <cyan>sudo sysctl -p<r>
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
limit.cur,
|
|
},
|
|
);
|
|
|
|
if (std.os.getenv("USER")) |user| {
|
|
if (user.len > 0) {
|
|
Output.prettyError(
|
|
\\
|
|
\\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf:
|
|
\\
|
|
\\ <cyan>{s} soft nofile 2147483646<r>
|
|
\\ <cyan>{s} hard nofile 2147483646<r>
|
|
\\
|
|
,
|
|
.{ user, user },
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Global.exit(1);
|
|
},
|
|
error.@"Invalid Bunfig" => {
|
|
Global.exit(1);
|
|
},
|
|
error.ProcessFdQuotaExceeded => {
|
|
const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit);
|
|
if (comptime Environment.isMac) {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
\\You may also need to run:
|
|
\\
|
|
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
limit.cur,
|
|
},
|
|
);
|
|
} else {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
\\That will only work for the current shell. To fix this for the entire system, run:
|
|
\\
|
|
\\ <cyan>sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf<r>
|
|
\\ <cyan>sudo sysctl -p<r>
|
|
\\
|
|
,
|
|
.{
|
|
limit.cur,
|
|
},
|
|
);
|
|
|
|
if (std.os.getenv("USER")) |user| {
|
|
if (user.len > 0) {
|
|
Output.prettyError(
|
|
\\
|
|
\\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf:
|
|
\\
|
|
\\ <cyan>{s} soft nofile 2147483646<r>
|
|
\\ <cyan>{s} hard nofile 2147483646<r>
|
|
\\
|
|
,
|
|
.{ user, user },
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Global.exit(1);
|
|
},
|
|
// The usage of `unreachable` in Zig's std.os may cause the file descriptor problem to show up as other errors
|
|
error.NotOpenForReading, error.Unexpected => {
|
|
const limit = std.os.getrlimit(.NOFILE) catch std.mem.zeroes(std.os.rlimit);
|
|
|
|
if (limit.cur > 0 and limit.cur < (8096 * 2)) {
|
|
Output.prettyError(
|
|
\\
|
|
\\<r><red>error<r>: An unknown error ocurred, possibly due to low max file descriptors <d>(<red>Unexpected<r><d>)<r>
|
|
\\
|
|
\\<d>Current limit: {d}<r>
|
|
\\
|
|
\\To fix this, try running:
|
|
\\
|
|
\\ <cyan>ulimit -n 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
limit.cur,
|
|
},
|
|
);
|
|
|
|
if (Environment.isLinux) {
|
|
if (std.os.getenv("USER")) |user| {
|
|
if (user.len > 0) {
|
|
Output.prettyError(
|
|
\\
|
|
\\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf:
|
|
\\
|
|
\\ <cyan>{s} soft nofile 2147483646<r>
|
|
\\ <cyan>{s} hard nofile 2147483646<r>
|
|
\\
|
|
,
|
|
.{
|
|
user,
|
|
user,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
} else if (Environment.isMac) {
|
|
Output.prettyError(
|
|
\\
|
|
\\If that still doesn't work, you may need to run:
|
|
\\
|
|
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
|
|
\\
|
|
,
|
|
.{},
|
|
);
|
|
}
|
|
|
|
Global.exit(1);
|
|
}
|
|
},
|
|
error.FileNotFound => {
|
|
Output.prettyError(
|
|
"\n<r><red>error<r><d>:<r> <b>FileNotFound<r>\nbun could not find a file, and the code that produces this error is missing a better error.\n",
|
|
.{},
|
|
);
|
|
Output.flush();
|
|
|
|
Report.printMetadata();
|
|
|
|
Output.flush();
|
|
|
|
print_stacktrace: {
|
|
var debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace;
|
|
var trace = @errorReturnTrace() orelse break :print_stacktrace;
|
|
Output.disableBuffering();
|
|
std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.debug.detectTTYConfig()) catch break :print_stacktrace;
|
|
}
|
|
|
|
Global.exit(1);
|
|
},
|
|
error.MissingPackageJSON => {
|
|
Output.prettyError(
|
|
"\n<r><red>error<r><d>:<r> <b>MissingPackageJSON<r>\nbun could not find a package.json file.\n",
|
|
.{},
|
|
);
|
|
Global.exit(1);
|
|
},
|
|
error.MissingValue => {
|
|
Global.exit(1);
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
Report.fatal(err, null);
|
|
|
|
print_stacktrace: {
|
|
var debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace;
|
|
var trace = @errorReturnTrace() orelse break :print_stacktrace;
|
|
Output.disableBuffering();
|
|
std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.debug.detectTTYConfig()) catch break :print_stacktrace;
|
|
}
|
|
|
|
Global.exit(1);
|
|
}
|