diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/crash/sentry.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff) | |
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/crash/sentry.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/crash/sentry.zig | 164 |
1 files changed, 110 insertions, 54 deletions
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(); |
