mirror of
https://github.com/zed-industries/zed.git
synced 2026-07-10 00:13:29 +00:00
Cleanup crashes crate (#54927)
Self-Review Checklist: - [ ] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [ ] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - N/A or Added/Fixed/Improved ...
This commit is contained in:
parent
d4f06ee374
commit
f40d7f0166
12 changed files with 371 additions and 273 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Arc<Client>> = 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<Vec<CrashServerMessage>> = 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<F: Future<Output = ()> + Send + Sync + 'static>(
|
||||
pub fn init<F, S, C, P>(
|
||||
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<Output = Arc<Client>> + use<F, C, S, P>
|
||||
where
|
||||
F: Future<Output = ()> + Send + Sync + 'static,
|
||||
C: (Fn(Duration) -> F) + Send + Sync + 'static,
|
||||
S: FnOnce(Pin<Box<dyn Future<Output = ()> + 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<F: Future<Output = ()> + Send + Sync + 'static>(
|
||||
fn connect_and_keepalive<F, C, S, P>(
|
||||
crash_init: InitCrashHandler,
|
||||
handler: CrashHandler,
|
||||
wait_timer: impl (Fn(Duration) -> F) + Send + Sync + 'static,
|
||||
) {
|
||||
socket_path: P,
|
||||
wait_timer: C,
|
||||
spawn: S,
|
||||
) -> impl Future<Output = Arc<Client>> + use<F, C, S, P>
|
||||
where
|
||||
F: Future<Output = ()> + Send + Sync + 'static,
|
||||
C: (Fn(Duration) -> F) + Send + Sync + 'static,
|
||||
S: FnOnce(Pin<Box<dyn Future<Output = ()> + 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<Any>"),
|
||||
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<Option<system_specs::GpuSpecs>>,
|
||||
user_info: Mutex<Option<UserInfo>>,
|
||||
has_connection: Arc<AtomicBool>,
|
||||
logs_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
|
|
@ -226,11 +206,7 @@ pub struct UserInfo {
|
|||
pub is_staff: Option<bool>,
|
||||
}
|
||||
|
||||
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<Client>, 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<Client>, 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<Client>, 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<Any>"));
|
||||
pub fn panic_hook(crash_client: Arc<Client>, 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("<unnamed>");
|
||||
|
||||
// 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(|| "<unknown>".to_owned(), |location| location.to_string());
|
||||
let location = location.map_or_else(|| "<unknown>".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),
|
||||
|
|
|
|||
|
|
@ -22,4 +22,4 @@ urlencoding.workspace = true
|
|||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
client.workspace = true
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
|
|
@ -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(());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<oneshot::Receiver<()>> = 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| {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ default = []
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
gpui.workspace = true
|
||||
human_bytes.workspace = true
|
||||
release_channel.workspace = true
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
pub fn new(
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
) -> Task<Self> {
|
||||
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<AppCommitSha>,
|
||||
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()),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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::<CrashHandler>() {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,10 @@ use zed_actions::{
|
|||
OpenZedUrl, Quit,
|
||||
};
|
||||
|
||||
pub struct CrashHandler(pub Arc<crashes::Client>);
|
||||
|
||||
impl gpui::Global for CrashHandler {}
|
||||
|
||||
actions!(
|
||||
zed,
|
||||
[
|
||||
|
|
@ -515,7 +519,9 @@ pub fn initialize_workspace(app_state: Arc<AppState>, 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::<CrashHandler>() {
|
||||
crashes::set_gpu_info(&crash_client.0, specs);
|
||||
}
|
||||
}
|
||||
|
||||
let edit_prediction_menu_handle = PopoverMenuHandle::default();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue