From 9f6a28bf44e6c021103537bb093ff805191ccc40 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 31 Aug 2024 14:14:30 -0700 Subject: [PATCH] crash: initialize sentry off-thread --- src/crash/sentry.zig | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/crash/sentry.zig b/src/crash/sentry.zig index e042cc9bf..4e244d71f 100644 --- a/src/crash/sentry.zig +++ b/src/crash/sentry.zig @@ -10,8 +10,15 @@ const state = &@import("../global.zig").state; const log = std.log.scoped(.sentry); +/// The global state for the Sentry SDK. This is unavoidable since crash +/// handling is a global process-wide thing. +var init_thread: ?std.Thread = null; + /// Process-wide initialization of our Sentry client. /// +/// This should only be called from one thread, and deinit should be called +/// from the same thread that calls init to avoid data races. +/// /// PRIVACY NOTE: I want to make it very clear that Ghostty by default does /// 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). @@ -29,6 +36,24 @@ pub fn init(gpa: Allocator) !void { // std.log.err("[sentry init time] start={}us duration={}ns", .{ start_micro, end.since(start) / std.time.ns_per_us }); // } + // 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( + .{}, + initThread, + .{gpa}, + ); + thr.setName("sentry-init") catch {}; + init_thread = thr; +} + +fn initThread(gpa: Allocator) !void { var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); const alloc = arena.allocator(); @@ -73,6 +98,11 @@ pub fn init(gpa: Allocator) !void { pub fn deinit() void { if (comptime builtin.os.tag == .windows) return; + // If we're still initializing then wait for init to finish. This + // is highly unlikely since init is a very fast operation but we want + // to avoid the possibility. + const thr = init_thread orelse return; + thr.join(); _ = sentry.c.sentry_close(); }