aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/crash
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/crash
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/crash')
-rw-r--r--vendor/libghostty-vt/src/crash/dir.zig28
-rw-r--r--vendor/libghostty-vt/src/crash/sentry.zig164
-rw-r--r--vendor/libghostty-vt/src/crash/sentry_envelope.zig6
3 files changed, 127 insertions, 71 deletions
diff --git a/vendor/libghostty-vt/src/crash/dir.zig b/vendor/libghostty-vt/src/crash/dir.zig
index e5a8f8a0..0f877ee3 100644
--- a/vendor/libghostty-vt/src/crash/dir.zig
+++ b/vendor/libghostty-vt/src/crash/dir.zig
@@ -1,12 +1,11 @@
const std = @import("std");
-const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const internal_os = @import("../os/main.zig");
/// Returns a Dir for the default directory. The Dir.path field must be
/// freed with the given allocator.
-pub fn defaultDir(alloc: Allocator) !Dir {
- const crash_dir = try internal_os.xdg.state(alloc, .{ .subdir = "ghostty/crash" });
+pub fn defaultDir(io: std.Io, alloc: Allocator, environ_map: *const std.process.Environ.Map) !Dir {
+ const crash_dir = try internal_os.xdg.state(io, alloc, environ_map, .{ .subdir = "ghostty/crash" });
errdefer alloc.free(crash_dir);
return .{ .path = crash_dir };
}
@@ -19,12 +18,13 @@ pub const Dir = struct {
/// Returns an iterator over the crash reports in this directory. This
/// iterator must be freed with `ReportIterator.deinit`. The iterator
/// may have no reports.
- pub fn iterator(self: *const Dir) !ReportIterator {
- var dir = std.fs.openDirAbsolute(
+ pub fn iterator(self: *const Dir, io: std.Io) !ReportIterator {
+ var dir = std.Io.Dir.openDirAbsolute(
+ io,
self.path,
.{ .iterate = true },
) catch return .{};
- errdefer dir.close();
+ errdefer dir.close(io);
return .{
.dir = dir,
@@ -34,28 +34,28 @@ pub const Dir = struct {
};
pub const ReportIterator = struct {
- dir: ?std.fs.Dir = null,
- it: std.fs.Dir.Iterator = undefined,
+ dir: ?std.Io.Dir = null,
+ it: std.Io.Dir.Iterator = undefined,
- pub fn deinit(self: *ReportIterator) void {
- if (self.dir) |dir| dir.close();
+ pub fn deinit(self: *ReportIterator, io: std.Io) void {
+ if (self.dir) |dir| dir.close(io);
}
- pub fn next(self: *ReportIterator) !?Report {
+ pub fn next(self: *ReportIterator, io: std.Io) !?Report {
// If we have no dir then we failed to open the directory.
const dir = self.dir orelse return null;
// Get the next file entry, if any.
const entry = entry: while (true) {
- const entry = try self.it.next() orelse return null;
+ const entry = try self.it.next(io) orelse return null;
if (entry.kind != .file) continue;
break :entry entry;
};
- const stat = try dir.statFile(entry.name);
+ const stat = try dir.statFile(io, entry.name, .{});
return .{
.name = entry.name,
- .mtime = stat.mtime,
+ .mtime = stat.mtime.toNanoseconds(),
};
}
};
diff --git a/vendor/libghostty-vt/src/crash/sentry.zig b/vendor/libghostty-vt/src/crash/sentry.zig
index 555b70fe..4181b5c1 100644
--- a/vendor/libghostty-vt/src/crash/sentry.zig
+++ b/vendor/libghostty-vt/src/crash/sentry.zig
@@ -7,7 +7,6 @@ const build_options = @import("build_options");
const sentry = if (build_options.sentry) @import("sentry");
const internal_os = @import("../os/main.zig");
const crash = @import("main.zig");
-const state = &@import("../global.zig").state;
const Surface = @import("../Surface.zig");
const log = std.log.scoped(.sentry);
@@ -16,6 +15,16 @@ const log = std.log.scoped(.sentry);
/// handling is a global process-wide thing.
var init_thread: ?std.Thread = null;
+/// Directory memory, holds the cache and state dirs persistently. This
+/// prevents any sort of crashes due to initialization races.
+var dir_mem: [std.fs.max_path_bytes * 2]u8 = undefined;
+
+/// Holds the XDG cache dir.
+var cache_dir_: ?[]const u8 = null;
+
+/// Holds the XDG state dir.
+var state_dir_: ?[]const u8 = null;
+
/// Thread-local state that can be set by thread main functions so that
/// crashes have more context.
///
@@ -46,40 +55,56 @@ pub threadlocal var thread_state: ?ThreadState = null;
/// NOT send any data over the network. We use the Sentry native SDK to collect
/// crash reports and logs, but we only store them locally (see Transport).
/// It is up to the user to grab the logs and manually send them to us
-/// (or they own Sentry instance) if they want to.
-pub fn init(gpa: Allocator) !void {
- if (comptime !build_options.sentry) return;
+/// (or to their own Sentry instance) if they want to.
+pub fn init(gpa: Allocator, environ_map: std.process.Environ.Map) !void {
+ if (comptime !build_options.sentry) {
+ var map = environ_map;
+ map.deinit();
+ return;
+ }
// Not supported on Windows currently, doesn't build.
- if (comptime builtin.os.tag == .windows) return;
-
- // const start = try std.time.Instant.now();
- // const start_micro = std.time.microTimestamp();
- // defer {
- // const end = std.time.Instant.now() catch unreachable;
- // // "[updateFrame critical time] <START us>\t<TIME_TAKEN us>"
- // std.log.err("[sentry init time] start={}us duration={}ns", .{ start_micro, end.since(start) / std.time.ns_per_us });
- // }
+ if (comptime builtin.os.tag == .windows) {
+ var map = environ_map;
+ map.deinit();
+ return;
+ }
// Must only start once
assert(init_thread == null);
- // We use a thread for initializing Sentry because initialization takes
- // ~2k ns on my M3 Max. That's not a LOT of time but it's enough to be
- // 90% of our pre-App startup time. Everything Sentry is doing initially
- // is safe to do on a separate thread and fast enough that its very
- // likely to be done before a crash occurs.
- const thr = try std.Thread.spawn(
+ // We use a thread for initializing Sentry because initialization is
+ // slow enough to matter for process startup: resolving our directories
+ // can take multiple milliseconds on macOS (Apple APIs) and Sentry's
+ // own init does disk I/O. Everything Sentry is doing initially is safe
+ // to do on a separate thread and fast enough that its very likely to
+ // be done before a crash occurs.
+ //
+ // The environ map is a snapshot owned by the thread (and freed there),
+ // so it is safe against concurrent mutations of the process environment
+ // (e.g. ensureLocale on the main thread).
+ const thr = std.Thread.spawn(
.{},
initThread,
- .{gpa},
- );
- thr.setName("sentry-init") catch {};
+ .{ gpa, environ_map },
+ ) catch |err| {
+ var map = environ_map;
+ map.deinit();
+ return err;
+ };
+
+ // Naming the thread from here only works on some platforms (e.g.
+ // Linux). On Darwin the thread names itself in initThread.
+ var single_threaded: std.Io.Threaded = .init_single_threaded;
+ defer single_threaded.deinit();
+ thr.setName(single_threaded.io(), "sentry-init") catch {};
+
init_thread = thr;
}
-fn initThread(gpa: Allocator) !void {
- if (comptime !build_options.sentry) return;
+fn initThread(gpa: Allocator, environ_map_: std.process.Environ.Map) !void {
+ var environ_map = environ_map_;
+ defer environ_map.deinit();
// Right now, on Darwin, `std.Thread.setName` can only name the current
// thread, and we have no way to get the current thread from within it,
@@ -88,9 +113,33 @@ fn initThread(gpa: Allocator) !void {
internal_os.macos.pthread_setname_np(&"sentry-init".*);
}
- var arena = std.heap.ArenaAllocator.init(gpa);
- defer arena.deinit();
- const alloc = arena.allocator();
+ // Get our directories.
+ var single_threaded: std.Io.Threaded = .init_single_threaded;
+ defer single_threaded.deinit();
+ var fba: std.heap.FixedBufferAllocator = .init(&dir_mem);
+
+ state_dir_ = state_dir: {
+ const dir = try crash.defaultDir(
+ single_threaded.io(),
+ gpa,
+ &environ_map,
+ );
+ defer gpa.free(dir.path);
+ break :state_dir try fba.allocator().dupe(u8, dir.path);
+ };
+ errdefer state_dir_ = null;
+
+ const cache_dir = cache_dir: {
+ const dir = try cacheDir(
+ single_threaded.io(),
+ gpa,
+ &environ_map,
+ );
+ defer gpa.free(dir);
+ break :cache_dir try fba.allocator().dupe(u8, dir);
+ };
+ cache_dir_ = cache_dir;
+ errdefer cache_dir_ = null;
const transport = sentry.Transport.init(&Transport.send);
// This will crash if the transport was never used so we avoid
@@ -112,24 +161,6 @@ fn initThread(gpa: Allocator) !void {
// do here and why we use this.
sentry.c.sentry_options_set_before_send(opts, beforeSend, null);
- // Determine the Sentry cache directory.
- const cache_dir = cache_dir: {
- // On macOS, we prefer to use the NSCachesDirectory value to be
- // a more idiomatic macOS application. But if XDG env vars are set
- // we will respect them.
- if (comptime builtin.os.tag == .macos) macos: {
- if (std.posix.getenv("XDG_CACHE_HOME") != null) break :macos;
- break :cache_dir try internal_os.macos.cacheDir(
- alloc,
- "sentry",
- );
- }
-
- break :cache_dir try internal_os.xdg.cache(
- alloc,
- .{ .subdir = "ghostty/sentry" },
- );
- };
sentry.c.sentry_options_set_database_path_n(
opts,
cache_dir.ptr,
@@ -154,6 +185,28 @@ fn initThread(gpa: Allocator) !void {
log.debug("sentry initialized database={s}", .{cache_dir});
}
+fn cacheDir(io: std.Io, alloc: Allocator, environ_map: *const std.process.Environ.Map) ![]const u8 {
+ // On macOS, we prefer to use the NSCachesDirectory value to be
+ // a more idiomatic macOS application. But if XDG env vars are set
+ // we will respect them.
+ if (comptime builtin.os.tag == .macos) macos: {
+ const xdg_cache_home = environ_map.get("XDG_CACHE_HOME") orelse break :macos;
+ if (xdg_cache_home.len > 0) {
+ return try internal_os.macos.cacheDir(
+ alloc,
+ "sentry",
+ );
+ }
+ }
+
+ return try internal_os.xdg.cache(
+ io,
+ alloc,
+ environ_map,
+ .{ .subdir = "ghostty/sentry" },
+ );
+}
+
/// Process-wide deinitialization of our Sentry client. This ensures all
/// our data is flushed.
pub fn deinit() void {
@@ -257,7 +310,13 @@ pub const Transport = struct {
/// Implementation of send but we can use Zig errors.
fn sendInternal(envelope: *sentry.Envelope) !void {
- var arena = std.heap.ArenaAllocator.init(state.alloc);
+ const state_dir = state_dir_ orelse return error.StateDirNotInitialized;
+
+ // The I/O and allocator we use here are just meant to get the job
+ // done for saving the crash report.
+ var single_threaded: std.Io.Threaded = .init_single_threaded;
+ defer single_threaded.deinit();
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
@@ -286,20 +345,17 @@ pub const Transport = struct {
// conflict.
const uuid = sentry.UUID.init();
- // Get our XDG state directory where we'll store the crash reports.
- // This directory must exist for writing to work.
- const dir = try crash.defaultDir(alloc);
- try std.fs.cwd().makePath(dir.path);
+ try std.Io.Dir.cwd().createDirPath(single_threaded.io(), state_dir);
// Build our final path and write to it.
const path = try std.fs.path.join(alloc, &.{
- dir.path,
+ state_dir,
try std.fmt.allocPrint(alloc, "{s}.ghosttycrash", .{uuid.string()}),
});
- const file = try std.fs.cwd().createFile(path, .{});
- defer file.close();
+ const file = try std.Io.Dir.cwd().createFile(single_threaded.io(), path, .{});
+ defer file.close(single_threaded.io());
var buf: [4096]u8 = undefined;
- var file_writer = file.writer(&buf);
+ var file_writer = file.writer(single_threaded.io(), &buf);
try file_writer.interface.writeAll(json);
try file_writer.end();
diff --git a/vendor/libghostty-vt/src/crash/sentry_envelope.zig b/vendor/libghostty-vt/src/crash/sentry_envelope.zig
index 08573b73..0472bcbe 100644
--- a/vendor/libghostty-vt/src/crash/sentry_envelope.zig
+++ b/vendor/libghostty-vt/src/crash/sentry_envelope.zig
@@ -93,7 +93,7 @@ pub const Envelope = struct {
alloc: Allocator,
reader: *std.Io.Reader,
) !std.ArrayList(Item) {
- var items: std.ArrayList(Item) = .{};
+ var items: std.ArrayList(Item) = .empty;
errdefer items.deinit(alloc);
while (try parseOneItem(alloc, reader)) |item| {
try items.append(alloc, item);
@@ -391,7 +391,7 @@ pub const Attachment = struct {
else => return error.InvalidFieldType,
} else null,
- .headers_extra = item.headers.unmanaged,
+ .headers_extra = item.headers,
.payload = item.payload,
};
}
@@ -417,7 +417,7 @@ pub const Attachment = struct {
}
return .{
- .headers = self.headers_extra.promote(alloc),
+ .headers = self.headers_extra,
.type = .attachment,
.payload = self.payload,
};