diff --git a/Cargo.lock b/Cargo.lock index 57bbd15efc8..38c7162a28a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4097,9 +4097,9 @@ dependencies = [ [[package]] name = "crash-handler" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2066907075af649bcb8bcb1b9b986329b243677e6918b2d920aa64b0aac5ace3" +checksum = "0df5c9639f4942eb7702b964b3f9adf03a55724a57558cc177407388a8b936e2" dependencies = [ "cfg-if", "crash-context", @@ -4113,15 +4113,11 @@ name = "crashes" version = "0.1.0" dependencies = [ "async-process", - "cfg-if", "crash-handler", - "futures 0.3.32", "log", "mach2 0.5.0", "minidumper", "parking_lot", - "paths", - "release_channel", "serde", "serde_json", "system_specs", @@ -6260,6 +6256,7 @@ dependencies = [ name = "feedback" version = "0.1.0" dependencies = [ + "client", "extension_host", "gpui", "system_specs", @@ -17561,7 +17558,6 @@ name = "system_specs" version = "0.1.0" dependencies = [ "anyhow", - "client", "gpui", "human_bytes", "pciid-parser", diff --git a/Cargo.toml b/Cargo.toml index 2fac513d4ee..72356e7ea35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -553,7 +553,7 @@ core-foundation = "=0.10.0" core-foundation-sys = "0.8.6" core-video = { version = "0.5.2", features = ["metal"] } cpal = "0.17" -crash-handler = "0.6" +crash-handler = "0.7" criterion = { version = "0.5", features = ["html_reports"] } ctor = "0.4.0" dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "1b461b310481d01e02b2603c16d7144b926339f8" } @@ -913,6 +913,7 @@ wasmtime = { opt-level = 3 } cranelift-codegen = { opt-level = 3 } wasmtime-environ = { opt-level = 3 } wasmtime-internal-cranelift = { opt-level = 3 } +minidumper = { opt-level = 3 } # Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster activity_indicator = { codegen-units = 1 } assets = { codegen-units = 1 } diff --git a/crates/crashes/Cargo.toml b/crates/crashes/Cargo.toml index 6886e5808aa..f8b898112c1 100644 --- a/crates/crashes/Cargo.toml +++ b/crates/crashes/Cargo.toml @@ -7,14 +7,10 @@ license = "GPL-3.0-or-later" [dependencies] async-process.workspace = true -cfg-if.workspace = true crash-handler.workspace = true -futures.workspace = true log.workspace = true minidumper.workspace = true parking_lot.workspace = true -paths.workspace = true -release_channel.workspace = true serde.workspace = true serde_json.workspace = true system_specs.workspace = true diff --git a/crates/crashes/src/crashes.rs b/crates/crashes/src/crashes.rs index 9abe7eb8b40..12c251e6f7f 100644 --- a/crates/crashes/src/crashes.rs +++ b/crates/crashes/src/crashes.rs @@ -1,55 +1,42 @@ use crash_handler::{CrashEventResult, CrashHandler}; -use futures::future::BoxFuture; use log::info; -use minidumper::{Client, LoopAction, MinidumpBinary, Server, SocketName}; +use minidumper::{LoopAction, MinidumpBinary, Server, SocketName}; use parking_lot::Mutex; -use release_channel::{RELEASE_CHANNEL, ReleaseChannel}; use serde::{Deserialize, Serialize}; -use std::mem; +use std::{panic::Location, pin::Pin}; -#[cfg(not(target_os = "windows"))] -use async_process::Command; use system_specs::GpuSpecs; -#[cfg(target_os = "macos")] -use std::sync::atomic::AtomicU32; use std::{ env, fs::{self, File}, - io, - panic::{self, PanicHookInfo}, + io, panic, path::{Path, PathBuf}, process::{self}, sync::{ - Arc, OnceLock, + Arc, atomic::{AtomicBool, Ordering}, }, thread, time::Duration, }; -// set once the crash handler has initialized and the client has connected to it -static CRASH_HANDLER: OnceLock> = OnceLock::new(); -// set when the first minidump request is made to avoid generating duplicate crash reports -pub static REQUESTED_MINIDUMP: AtomicBool = AtomicBool::new(false); +pub use minidumper::Client; + const CRASH_HANDLER_PING_TIMEOUT: Duration = Duration::from_secs(60); const CRASH_HANDLER_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); -static PENDING_CRASH_SERVER_MESSAGES: Mutex> = Mutex::new(Vec::new()); - -#[cfg(target_os = "macos")] -static PANIC_THREAD_ID: AtomicU32 = AtomicU32::new(0); - -fn should_install_crash_handler() -> bool { - if let Ok(value) = env::var("ZED_GENERATE_MINIDUMPS") { - return value == "true" || value == "1"; - } - - if *RELEASE_CHANNEL == ReleaseChannel::Dev { - return false; - } - - true +/// Force a backtrace to be printed on panic. +pub fn force_backtrace() { + let old_hook = panic::take_hook(); + panic::set_hook(Box::new(move |info| { + unsafe { env::set_var("RUST_BACKTRACE", "1") }; + old_hook(info); + // prevent the macOS crash dialog from popping up + if cfg!(target_os = "macos") { + std::process::exit(1); + } + })); } /// Install crash signal handlers and spawn the crash-handler subprocess. @@ -57,133 +44,125 @@ fn should_install_crash_handler() -> bool { /// The synchronous portion (signal handlers, panic hook) runs inline. /// The async keepalive task is passed to `spawn` so the caller decides /// which executor to schedule it on. -pub fn init + Send + Sync + 'static>( +pub fn init( crash_init: InitCrashHandler, - spawn: impl FnOnce(BoxFuture<'static, ()>), - wait_timer: impl (Fn(Duration) -> F) + Send + Sync + 'static, -) { - if !should_install_crash_handler() { - let old_hook = panic::take_hook(); - panic::set_hook(Box::new(move |info| { - unsafe { env::set_var("RUST_BACKTRACE", "1") }; - old_hook(info); - // prevent the macOS crash dialog from popping up - if cfg!(target_os = "macos") { - std::process::exit(1); - } - })); - return; - } - - panic::set_hook(Box::new(panic_hook)); - - let handler = CrashHandler::attach(unsafe { - crash_handler::make_crash_event(move |crash_context: &crash_handler::CrashContext| { - let Some(client) = CRASH_HANDLER.get() else { - return CrashEventResult::Handled(false); - }; - - // only request a minidump once - let res = if REQUESTED_MINIDUMP - .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - { - #[cfg(target_os = "macos")] - suspend_all_other_threads(); - - // on macos this "ping" is needed to ensure that all our - // `client.send_message` calls have been processed before we trigger the - // minidump request. - client.ping().ok(); - client.request_dump(crash_context).is_ok() - } else { - true - }; - CrashEventResult::Handled(res) - }) - }) - .expect("failed to attach signal handler"); - - info!("crash signal handlers installed"); - - spawn(Box::pin(connect_and_keepalive( - crash_init, handler, wait_timer, - ))); + spawn: S, + socket_path: P, + wait_timer: C, +) -> impl Future> + use +where + F: Future + Send + Sync + 'static, + C: (Fn(Duration) -> F) + Send + Sync + 'static, + S: FnOnce(Pin + Send + 'static>>), + P: FnOnce(u32) -> PathBuf, +{ + connect_and_keepalive(crash_init, socket_path, wait_timer, spawn) } /// Spawn the crash-handler subprocess, connect the IPC client, and run the /// keepalive ping loop. Called on a background executor by [`init`]. -async fn connect_and_keepalive + Send + Sync + 'static>( +fn connect_and_keepalive( crash_init: InitCrashHandler, - handler: CrashHandler, - wait_timer: impl (Fn(Duration) -> F) + Send + Sync + 'static, -) { + socket_path: P, + wait_timer: C, + spawn: S, +) -> impl Future> + use +where + F: Future + Send + Sync + 'static, + C: (Fn(Duration) -> F) + Send + Sync + 'static, + S: FnOnce(Pin + Send + 'static>>), + P: FnOnce(u32) -> PathBuf, +{ let exe = env::current_exe().expect("unable to find ourselves"); - let zed_pid = process::id(); - let socket_name = paths::temp_dir().join(format!("zed-crash-handler-{zed_pid}")); - #[cfg(not(target_os = "windows"))] - let _crash_handler = Command::new(exe) - .arg("--crash-handler") - .arg(&socket_name) - .spawn() - .expect("unable to spawn server process"); - - #[cfg(target_os = "windows")] - spawn_crash_handler_windows(&exe, &socket_name); - + let socket_path = socket_path(process::id()); + let mut _crash_handler = spawn_crash_handler(&exe, &socket_path); info!("spawning crash handler process"); - send_crash_server_message(CrashServerMessage::Init(crash_init)); + async move { + let mut elapsed = Duration::ZERO; + let retry_frequency = Duration::from_millis(100); + let client = loop { + if let Ok(client) = Client::with_name(SocketName::Path(&socket_path)) { + info!("connected to crash handler process after {elapsed:?}"); + break client; + } + elapsed += retry_frequency; + wait_timer(retry_frequency).await; + }; + let client = Arc::new(client); - let mut elapsed = Duration::ZERO; - let retry_frequency = Duration::from_millis(100); - let mut maybe_client = None; - while maybe_client.is_none() { - if let Ok(client) = Client::with_name(SocketName::Path(&socket_name)) { - maybe_client = Some(client); - info!("connected to crash handler process after {elapsed:?}"); - break; - } - elapsed += retry_frequency; - wait_timer(retry_frequency).await; - } - let client = maybe_client.unwrap(); - let client = Arc::new(client); + panic::set_hook({ + let client = client.clone(); + Box::new(move |payload| { + panic_hook( + client.clone(), + payload.payload_as_str().unwrap_or("Box"), + payload.location(), + ) + }) + }); + info!("panic handler registered"); + let handler = CrashHandler::attach(unsafe { + let client = client.clone(); + let handler = move |crash_context: &crash_handler::CrashContext| { + // set when the first minidump request is made to avoid generating duplicate crash reports + static REQUESTED_MINIDUMP: AtomicBool = AtomicBool::new(false); - #[cfg(target_os = "linux")] - handler.set_ptracer(Some(_crash_handler.id())); + // only request a minidump once + let res = if REQUESTED_MINIDUMP + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + #[cfg(target_os = "macos")] + macos::suspend_all_other_threads(); - // Publishing the client to the OnceLock makes it visible to the signal - // handler callback installed earlier. - CRASH_HANDLER.set(client.clone()).ok(); - let messages: Vec<_> = mem::take(PENDING_CRASH_SERVER_MESSAGES.lock().as_mut()); - for message in messages.into_iter() { - send_crash_server_message(message); - } - // mem::forget so that the drop is not called - mem::forget(handler); - info!("crash handler registered"); + // on macos this "ping" is needed to ensure that all our + // `client.send_message` calls have been processed before we trigger the + // minidump request. + client.ping().ok(); + let r = client.request_dump(crash_context); + if let Err(e) = &r { + eprintln!("failed to request dump: {:?}", e); + } + #[cfg(target_os = "macos")] + macos::resume_all_other_threads(); + r.is_ok() + } else { + true + }; + CrashEventResult::Handled(res) + }; + crash_handler::make_crash_event(handler) + }) + .expect("failed to attach signal handler"); - loop { - client.ping().ok(); - wait_timer(Duration::from_secs(10)).await; - } -} + info!("crash signal handlers installed"); + send_crash_server_message(&client, CrashServerMessage::Init(crash_init)); -#[cfg(target_os = "macos")] -unsafe fn suspend_all_other_threads() { - let task = unsafe { mach2::traps::current_task() }; - let mut threads: mach2::mach_types::thread_act_array_t = std::ptr::null_mut(); - let mut count = 0; - unsafe { - mach2::task::task_threads(task, &raw mut threads, &raw mut count); - } - let current = unsafe { mach2::mach_init::mach_thread_self() }; - let panic_thread = PANIC_THREAD_ID.load(Ordering::SeqCst); - for i in 0..count { - let t = unsafe { *threads.add(i as usize) }; - if t != current && t != panic_thread { - unsafe { mach2::thread_act::thread_suspend(t) }; - } + #[cfg(target_os = "linux")] + handler.set_ptracer(Some(_crash_handler.id())); + + info!("crash handler registered"); + spawn(Box::pin({ + let client = client.clone(); + async move { + let _handler = { handler }; + loop { + if let Err(e) = client.ping() { + #[cfg(not(target_os = "windows"))] + log::error!( + "ping failed: {:?}, process exit status: {:?}", + e, + _crash_handler.try_status() + ); + #[cfg(target_os = "windows")] + log::error!("ping failed: {:?}", e,); + break; + }; + wait_timer(Duration::from_secs(10)).await; + } + } + })); + client } } @@ -193,6 +172,7 @@ pub struct CrashServer { active_gpu: Mutex>, user_info: Mutex>, has_connection: Arc, + logs_dir: PathBuf, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -226,11 +206,7 @@ pub struct UserInfo { pub is_staff: Option, } -fn send_crash_server_message(message: CrashServerMessage) { - let Some(crash_server) = CRASH_HANDLER.get() else { - PENDING_CRASH_SERVER_MESSAGES.lock().push(message); - return; - }; +fn send_crash_server_message(crash_client: &Arc, message: CrashServerMessage) { let data = match serde_json::to_vec(&message) { Ok(data) => data, Err(err) => { @@ -239,17 +215,17 @@ fn send_crash_server_message(message: CrashServerMessage) { } }; - if let Err(err) = crash_server.send_message(0, data) { + if let Err(err) = crash_client.send_message(0, data) { log::warn!("Failed to send data to crash server {:?}", err); } } -pub fn set_gpu_info(specs: GpuSpecs) { - send_crash_server_message(CrashServerMessage::GPUInfo(specs)); +pub fn set_gpu_info(crash_client: &Arc, specs: GpuSpecs) { + send_crash_server_message(crash_client, CrashServerMessage::GPUInfo(specs)); } -pub fn set_user_info(info: UserInfo) { - send_crash_server_message(CrashServerMessage::UserInfo(info)); +pub fn set_user_info(crash_client: &Arc, info: UserInfo) { + send_crash_server_message(crash_client, CrashServerMessage::UserInfo(info)); } #[derive(Serialize, Deserialize, Debug)] @@ -262,7 +238,8 @@ enum CrashServerMessage { impl minidumper::ServerHandler for CrashServer { fn create_minidump_file(&self) -> Result<(File, PathBuf), io::Error> { - let dump_path = paths::logs_dir() + let dump_path = self + .logs_dir .join( &self .initialization_params @@ -318,7 +295,8 @@ impl minidumper::ServerHandler for CrashServer { user_info: self.user_info.lock().clone(), }; - let crash_data_path = paths::logs_dir() + let crash_data_path = self + .logs_dir .join(&crash_info.init.session_id) .with_extension("json"); @@ -382,52 +360,92 @@ fn strip_user_string_from_panic(message: &str) -> String { message.to_owned() } -pub fn panic_hook(info: &PanicHookInfo) { - let message = strip_user_string_from_panic(info.payload_as_str().unwrap_or("Box")); +pub fn panic_hook(crash_client: Arc, message: &str, location: Option<&Location>) { + let message = strip_user_string_from_panic(message); - let span = info - .location() + let span = location .map(|loc| format!("{}:{}", loc.file(), loc.line())) .unwrap_or_default(); let current_thread = std::thread::current(); let thread_name = current_thread.name().unwrap_or(""); - // wait 500ms for the crash handler process to start up - // if it's still not there just write panic info and no minidump - let retry_frequency = Duration::from_millis(100); - for _ in 0..5 { - if CRASH_HANDLER.get().is_some() { - break; - } - thread::sleep(retry_frequency); - } - let location = info - .location() - .map_or_else(|| "".to_owned(), |location| location.to_string()); + let location = location.map_or_else(|| "".to_owned(), |location| location.to_string()); log::error!("thread '{thread_name}' panicked at {location}:\n{message}..."); - send_crash_server_message(CrashServerMessage::Panic(CrashPanic { message, span })); + send_crash_server_message( + &crash_client, + CrashServerMessage::Panic(CrashPanic { message, span }), + ); log::error!("triggering a crash to generate a minidump..."); #[cfg(target_os = "macos")] - PANIC_THREAD_ID.store( - unsafe { mach2::mach_init::mach_thread_self() }, - Ordering::SeqCst, - ); - - cfg_if::cfg_if! { - if #[cfg(target_os = "windows")] { - // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- - CrashHandler.simulate_exception(Some(234)); // (MORE_DATA_AVAILABLE) - } else { - std::process::abort(); - } + macos::set_panic_thread_id(); + #[cfg(target_os = "windows")] + { + // https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- + CrashHandler.simulate_exception(Some(234)); // (MORE_DATA_AVAILABLE) + } + #[cfg(not(target_os = "windows"))] + { + std::process::abort(); } } +#[cfg(target_os = "macos")] +mod macos { + static PANIC_THREAD_ID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + + pub(super) fn set_panic_thread_id() { + PANIC_THREAD_ID.store( + unsafe { mach2::mach_init::mach_thread_self() }, + std::sync::atomic::Ordering::Release, + ); + } + + pub(super) unsafe fn suspend_all_other_threads() { + let task = unsafe { mach2::traps::current_task() }; + let mut threads: mach2::mach_types::thread_act_array_t = std::ptr::null_mut(); + let mut count = 0; + unsafe { + mach2::task::task_threads(task, &raw mut threads, &raw mut count); + } + let current = unsafe { mach2::mach_init::mach_thread_self() }; + for i in 0..count { + let t = unsafe { *threads.add(i as usize) }; + if t != current { + unsafe { mach2::thread_act::thread_suspend(t) }; + } + } + } + + pub(super) unsafe fn resume_all_other_threads() { + let task = unsafe { mach2::traps::current_task() }; + let mut threads: mach2::mach_types::thread_act_array_t = std::ptr::null_mut(); + let mut count = 0; + unsafe { + mach2::task::task_threads(task, &raw mut threads, &raw mut count); + } + let current = unsafe { mach2::mach_init::mach_thread_self() }; + for i in 0..count { + let t = unsafe { *threads.add(i as usize) }; + if t != current { + unsafe { mach2::thread_act::thread_resume(t) }; + } + } + } +} +#[cfg(not(target_os = "windows"))] +fn spawn_crash_handler(exe: &Path, socket_name: &Path) -> async_process::Child { + async_process::Command::new(exe) + .arg("--crash-handler") + .arg(&socket_name) + .spawn() + .expect("unable to spawn server process") +} + #[cfg(target_os = "windows")] -fn spawn_crash_handler_windows(exe: &Path, socket_name: &Path) { +fn spawn_crash_handler(exe: &Path, socket_name: &Path) { use std::ffi::OsStr; use std::iter::once; use std::os::windows::ffi::OsStrExt; @@ -477,7 +495,7 @@ fn spawn_crash_handler_windows(exe: &Path, socket_name: &Path) { } } -pub fn crash_server(socket: &Path) { +pub fn crash_server(socket: &Path, logs_dir: PathBuf) { let Ok(mut server) = Server::with_name(SocketName::Path(socket)) else { log::info!("Couldn't create socket, there may already be a running crash server"); return; @@ -508,6 +526,7 @@ pub fn crash_server(socket: &Path) { user_info: Mutex::default(), has_connection, active_gpu: Mutex::default(), + logs_dir, }), &shutdown, Some(CRASH_HANDLER_PING_TIMEOUT), diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index 6a595bf4ee4..981fc4ea41c 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -22,4 +22,4 @@ urlencoding.workspace = true util.workspace = true workspace.workspace = true zed_actions.workspace = true - +client.workspace = true diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 4a9f92d6d26..16c91ce769d 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -1,3 +1,4 @@ +use client::telemetry; use extension_host::ExtensionStore; use gpui::{App, ClipboardItem, PromptLevel, actions}; use system_specs::{CopySystemSpecsIntoClipboard, SystemSpecs}; @@ -48,7 +49,8 @@ pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, _, _| { workspace .register_action(|_, _: &CopySystemSpecsIntoClipboard, window, cx| { - let specs = SystemSpecs::new(window, cx); + let specs = + SystemSpecs::new(window, cx, telemetry::os_name(), telemetry::os_version()); cx.spawn_in(window, async move |_, cx| { let specs = specs.await.to_string(); @@ -83,7 +85,8 @@ pub fn init(cx: &mut App) { cx.open_url(REQUEST_FEATURE_URL); }) .register_action(move |_, _: &FileBugReport, window, cx| { - let specs = SystemSpecs::new(window, cx); + let specs = + SystemSpecs::new(window, cx, telemetry::os_name(), telemetry::os_version()); cx.spawn_in(window, async move |_, cx| { let specs = specs.await; cx.update(|_, cx| { @@ -94,7 +97,8 @@ pub fn init(cx: &mut App) { .detach(); }) .register_action(move |_, _: &EmailZed, window, cx| { - let specs = SystemSpecs::new(window, cx); + let specs = + SystemSpecs::new(window, cx, telemetry::os_name(), telemetry::os_version()); cx.spawn_in(window, async move |_, cx| { let specs = specs.await; cx.update(|_, cx| { diff --git a/crates/remote_server/src/main.rs b/crates/remote_server/src/main.rs index 66ffcc16317..5f0033f1a0a 100644 --- a/crates/remote_server/src/main.rs +++ b/crates/remote_server/src/main.rs @@ -30,7 +30,7 @@ fn main() -> anyhow::Result<()> { } if let Some(socket) = &cli.crash_handler { - crashes::crash_server(socket.as_path()); + crashes::crash_server(socket.as_path(), paths::logs_dir().clone()); return Ok(()); } diff --git a/crates/remote_server/src/server.rs b/crates/remote_server/src/server.rs index c0a6d6de199..ec2b1963b9d 100644 --- a/crates/remote_server/src/server.rs +++ b/crates/remote_server/src/server.rs @@ -462,21 +462,35 @@ pub fn execute_run( let app = gpui_platform::headless(); let pid = std::process::id(); let id = pid.to_string(); - crashes::init( - crashes::InitCrashHandler { - session_id: id, - zed_version: VERSION.to_owned(), - binary: "zed-remote-server".to_string(), - release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), - commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(), - }, - |task| { - app.background_executor().spawn(task).detach(); - }, - // we are running outside gpui - #[allow(clippy::disallowed_methods)] - |duration| FutureExt::map(Timer::after(duration), |_| ()), - ); + let should_install_crash_handler = matches!( + env::var("ZED_GENERATE_MINIDUMPS").as_deref(), + Ok("true" | "1") + ) || *RELEASE_CHANNEL != ReleaseChannel::Dev; + + let crash_handler = if should_install_crash_handler { + Some(app.background_executor().spawn(crashes::init( + crashes::InitCrashHandler { + session_id: id, + zed_version: VERSION.to_owned(), + binary: "zed-remote-server".to_string(), + release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), + commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(), + }, + { + let background_executor = app.background_executor(); + move |task| { + background_executor.spawn(task).detach(); + } + }, + |pid| paths::temp_dir().join(format!("zed-remote-server-crash-handler-{pid}")), + // we are running outside gpui + #[allow(clippy::disallowed_methods)] + |duration| FutureExt::map(Timer::after(duration), |_| ()), + ))) + } else { + crashes::force_backtrace(); + None + }; let log_rx = init_logging_server(&log_file)?; log::info!( "starting up with PID {}:\npid_file: {:?}, log_file: {:?}, stdin_socket: {:?}, stdout_socket: {:?}, stderr_socket: {:?}", @@ -515,7 +529,14 @@ pub fn execute_run( let shell_env_loaded_rx: Option> = None; let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new()); - let run = move |cx: &mut _| { + let run = move |cx: &mut App| { + if let Some(crash_handler) = crash_handler { + cx.spawn(async move |_cx| { + let _crash_handler = crash_handler.await; + // cx.update(|cx| cx.set_global(CrashHandler(crash_handler))) + }) + .detach(); + } settings::init(cx); let app_commit_sha = option_env!("ZED_COMMIT_SHA").map(|s| AppCommitSha::new(s.to_owned())); let app_version = AppVersion::load( @@ -720,22 +741,30 @@ pub(crate) fn execute_proxy( let server_paths = ServerPaths::new(&identifier)?; let id = std::process::id().to_string(); - crashes::init( - crashes::InitCrashHandler { - session_id: id, - zed_version: VERSION.to_owned(), - binary: "zed-remote-server".to_string(), - release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), - commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(), - }, - |task| { - smol::spawn(task).detach(); - }, - // we are running outside gpui - #[allow(clippy::disallowed_methods)] - |duration| FutureExt::map(Timer::after(duration), |_| ()), - ); + let should_install_crash_handler = matches!( + env::var("ZED_GENERATE_MINIDUMPS").as_deref(), + Ok("true" | "1") + ) || *RELEASE_CHANNEL != ReleaseChannel::Dev; + if should_install_crash_handler { + smol::spawn(crashes::init( + crashes::InitCrashHandler { + session_id: id, + zed_version: VERSION.to_owned(), + binary: "zed-remote-proxy".to_string(), + release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), + commit_sha: option_env!("ZED_COMMIT_SHA").unwrap_or("no_sha").to_owned(), + }, + |task| { + smol::spawn(task).detach(); + }, + |pid| paths::temp_dir().join(format!("zed-remote-server-proxy-crash-handler-{pid}")), + // we are running outside gpui + #[allow(clippy::disallowed_methods)] + |duration| FutureExt::map(Timer::after(duration), |_| ()), + )) + .detach(); + }; log::info!("starting proxy process. PID: {}", std::process::id()); let server_pid = { let server_pid = check_pid_file(&server_paths.pid_file).map_err(|source| { diff --git a/crates/system_specs/Cargo.toml b/crates/system_specs/Cargo.toml index 15d6822b387..7af62d934af 100644 --- a/crates/system_specs/Cargo.toml +++ b/crates/system_specs/Cargo.toml @@ -16,7 +16,6 @@ default = [] [dependencies] anyhow.workspace = true -client.workspace = true gpui.workspace = true human_bytes.workspace = true release_channel.workspace = true diff --git a/crates/system_specs/src/system_specs.rs b/crates/system_specs/src/system_specs.rs index 139f23d1937..2c7a169ef01 100644 --- a/crates/system_specs/src/system_specs.rs +++ b/crates/system_specs/src/system_specs.rs @@ -1,4 +1,3 @@ -use client::telemetry; pub use gpui::GpuSpecs; use gpui::{App, AppContext as _, Task, Window, actions}; use human_bytes::human_bytes; @@ -30,10 +29,14 @@ pub struct SystemSpecs { } impl SystemSpecs { - pub fn new(window: &mut Window, cx: &mut App) -> Task { + pub fn new( + window: &mut Window, + cx: &mut App, + os_name: String, + os_version: String, + ) -> Task { let app_version = AppVersion::global(cx).to_string(); let release_channel = ReleaseChannel::global(cx); - let os_name = telemetry::os_name(); let system = System::new_with_specifics( RefreshKind::nothing().with_memory(MemoryRefreshKind::everything()), ); @@ -55,7 +58,6 @@ impl SystemSpecs { }); cx.background_spawn(async move { - let os_version = telemetry::os_version(); SystemSpecs { app_version, release_channel: release_channel.display_name(), @@ -74,9 +76,9 @@ impl SystemSpecs { app_version: Version, app_commit_sha: Option, release_channel: ReleaseChannel, + os_name: String, + os_version: String, ) -> Self { - let os_name = telemetry::os_name(); - let os_version = telemetry::os_version(); let system = System::new_with_specifics( RefreshKind::nothing().with_memory(MemoryRefreshKind::everything()), ); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9141fe1aa8a..f8023224a33 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -23,6 +23,7 @@ use git::GitHostingProviderRegistry; use git_ui::clone::clone_and_open; use gpui::{ App, AppContext, Application, AsyncApp, Focusable as _, QuitMode, Task, UpdateGlobal as _, + block_on, }; use gpui_platform; @@ -43,6 +44,7 @@ use recent_projects::{RemoteSettings, open_remote_project}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use session::{AppSession, Session}; use settings::{BaseKeymap, Settings, SettingsStore, watch_config_file}; +use smol::future::poll_once; use std::{ cell::RefCell, env, @@ -67,7 +69,7 @@ use zed::{ handle_keymap_file_changes, initialize_workspace, open_paths_with_positions, }; -use crate::zed::{OpenRequestKind, eager_load_active_theme_and_icon_theme}; +use crate::zed::{CrashHandler, OpenRequestKind, eager_load_active_theme_and_icon_theme}; #[cfg(feature = "mimalloc")] #[global_allocator] @@ -195,7 +197,7 @@ fn main() { // `zed --crash-handler` Makes zed operate in minidump crash handler mode if let Some(socket) = &args.crash_handler { - crashes::crash_server(socket.as_path()); + crashes::crash_server(socket.as_path(), paths::logs_dir().clone()); return; } @@ -299,6 +301,8 @@ fn main() { app_version, app_commit_sha, *release_channel::RELEASE_CHANNEL, + client::telemetry::os_name(), + client::telemetry::os_version(), ); println!("Zed System Specs (from CLI):\n{}", system_specs); return; @@ -338,28 +342,6 @@ fn main() { KeyValueStore::from_app_db(&app_db), )); let background_executor = app.background_executor(); - crashes::init( - InitCrashHandler { - session_id, - // strip the build and channel information from the version string, we send them separately - zed_version: semver::Version::new( - app_version.major, - app_version.minor, - app_version.patch, - ) - .to_string(), - binary: "zed".to_string(), - release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), - commit_sha: app_commit_sha - .as_ref() - .map(|sha| sha.full()) - .unwrap_or_else(|| "no sha".to_owned()), - }, - |task| { - app.background_executor().spawn(task).detach(); - }, - move |duration| background_executor.timer(duration), - ); let (open_listener, mut open_rx) = OpenListener::new(); @@ -389,6 +371,46 @@ fn main() { return; } + let should_install_crash_handler = matches!( + env::var("ZED_GENERATE_MINIDUMPS").as_deref(), + Ok("true" | "1") + ) || *release_channel::RELEASE_CHANNEL + != ReleaseChannel::Dev; + + let crash_handler = if should_install_crash_handler { + Some( + app.background_executor().spawn(crashes::init( + InitCrashHandler { + session_id, + // strip the build and channel information from the version string, we send them separately + zed_version: semver::Version::new( + app_version.major, + app_version.minor, + app_version.patch, + ) + .to_string(), + binary: "zed".to_string(), + release_channel: release_channel::RELEASE_CHANNEL_NAME.clone(), + commit_sha: app_commit_sha + .as_ref() + .map(|sha| sha.full()) + .unwrap_or_else(|| "no sha".to_owned()), + }, + { + let background_executor1 = app.background_executor(); + move |task| { + background_executor1.spawn(task).detach(); + } + }, + |pid| paths::temp_dir().join(format!("zed-crash-handler-{pid}")), + move |duration| background_executor.timer(duration), + )), + ) + } else { + crashes::force_backtrace(); + None + }; + let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new()); let git_binary_path = if cfg!(target_os = "macos") && option_env!("ZED_BUNDLE").as_deref() == Some("true") { @@ -417,7 +439,7 @@ fn main() { util::load_login_shell_environment().await.log_err(); shell_env_loaded_tx.send(()).ok(); }) - .detach() + .detach(); } else { drop(shell_env_loaded_tx) } @@ -573,12 +595,17 @@ fn main() { ); cx.subscribe(&user_store, { let telemetry = telemetry.clone(); - move |_, evt: &client::user::Event, _| match evt { + move |_, evt: &client::user::Event, cx| match evt { client::user::Event::PrivateUserInfoUpdated => { - crashes::set_user_info(crashes::UserInfo { - metrics_id: telemetry.metrics_id().map(|s| s.to_string()), - is_staff: telemetry.is_staff(), - }); + if let Some(crash_client) = cx.try_global::() { + crashes::set_user_info( + &crash_client.0, + crashes::UserInfo { + metrics_id: telemetry.metrics_id().map(|s| s.to_string()), + is_staff: telemetry.is_staff(), + }, + ); + } } _ => {} } @@ -810,6 +837,25 @@ fn main() { let menus = app_menus(cx); cx.set_menus(menus); + + if let Some(mut crash_handler) = crash_handler { + let crash_handler2 = block_on(poll_once(&mut crash_handler)); + match crash_handler2 { + Some(crash_handler) => { + cx.set_global(CrashHandler(crash_handler)); + } + None => { + cx.spawn(async move |cx| { + let client1 = crash_handler.await; + cx.update(|cx| { + cx.set_global(CrashHandler(client1)); + }); + }) + .detach(); + } + } + } + initialize_workspace(app_state.clone(), cx); cx.activate(true); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 6d1a9c176f1..9461c89b7ad 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -100,6 +100,10 @@ use zed_actions::{ OpenZedUrl, Quit, }; +pub struct CrashHandler(pub Arc); + +impl gpui::Global for CrashHandler {} + actions!( zed, [ @@ -515,7 +519,9 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut App) { if let Some(specs) = window.gpu_specs() { log::info!("Using GPU: {:?}", specs); show_software_emulation_warning_if_needed(specs.clone(), window, cx); - crashes::set_gpu_info(specs); + if let Some(crash_client) = cx.try_global::() { + crashes::set_gpu_info(&crash_client.0, specs); + } } let edit_prediction_menu_handle = PopoverMenuHandle::default();