mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-24 16:11:43 +00:00
crash: resolve sentry directories on the init thread
Sentry initialization already ran on a separate thread, but the cache and state directory resolution happened on the main thread before spawning it. On macOS the cache dir resolution calls NSFileManager URLForDirectory:inDomain:appropriateForURL:create:error: which takes multiple milliseconds and was the single largest cost in global.init. All directory resolution now happens on the init thread. before: 2967us-4018us after: 30us-70us (env map snapshot + thread spawn) global.init total drops from ~3.4-5.0ms to ~0.4-1.0ms.
This commit is contained in:
@@ -56,52 +56,55 @@ pub threadlocal var thread_state: ?ThreadState = null;
|
||||
/// 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 to their own Sentry instance) if they want to.
|
||||
pub fn init(gpa: Allocator, environ_map: *const std.process.Environ.Map) !void {
|
||||
if (comptime !build_options.sentry) return;
|
||||
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;
|
||||
if (comptime builtin.os.tag == .windows) {
|
||||
var map = environ_map;
|
||||
map.deinit();
|
||||
return;
|
||||
}
|
||||
|
||||
// Must only start once
|
||||
assert(init_thread == null);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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,
|
||||
.{cache_dir},
|
||||
);
|
||||
.{ 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(cache_dir: []const u8) !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,
|
||||
@@ -110,6 +113,34 @@ fn initThread(cache_dir: []const u8) !void {
|
||||
internal_os.macos.pthread_setname_np(&"sentry-init".*);
|
||||
}
|
||||
|
||||
// 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
|
||||
// that for now. This probably leaks some memory but it'd be very
|
||||
|
||||
@@ -181,10 +181,10 @@ pub fn init(opts: InitOpts) !void {
|
||||
self.rlimits = .init();
|
||||
|
||||
if (build_options.sentry) {
|
||||
// Initialize our crash reporting.
|
||||
var environ_map = try self.environ.createMap(self.alloc);
|
||||
defer environ_map.deinit();
|
||||
crash.init(self.alloc, &environ_map) catch |err| {
|
||||
// Initialize our crash reporting. The environ map snapshot is
|
||||
// owned by crash.init (it is freed by the init thread).
|
||||
const environ_map = try self.environ.createMap(self.alloc);
|
||||
crash.init(self.alloc, environ_map) catch |err| {
|
||||
std.log.warn(
|
||||
"sentry init failed, no crash capture available err={}",
|
||||
.{err},
|
||||
|
||||
Reference in New Issue
Block a user