Improve panic message usefulness on local dev builds (#2819)

Julia created

I got tired of having to hack in a panic hook bypass whenever I wanted a
backtrace with line numbers. Now a dev channel build will behave more
like the default panic hook, printing a pretty traditional backtrace
message and exit with an error code instead of aborting to avoid the
annoying "Zed crashed" dialog.

I have plans to modify our panic reporting to be able to have line
numbers reported without breaking the de-duping but I haven't done that
yet.

Additionally I slightly improved what we do in threads which panic as a
result of another thread's panic.

Release Notes:

- N/A

Change summary

crates/zed/src/main.rs | 42 +++++++++++++++++++++++++++++++-----------
1 file changed, 31 insertions(+), 11 deletions(-)

Detailed changes

crates/zed/src/main.rs 🔗

@@ -45,6 +45,7 @@ use std::{
 use sum_tree::Bias;
 use terminal_view::{get_working_directory, TerminalSettings, TerminalView};
 use util::{
+    channel::ReleaseChannel,
     http::{self, HttpClient},
     paths::PathLikeWithPosition,
 };
@@ -415,22 +416,41 @@ fn init_panic_hook(app: &App, installation_id: Option<String>) {
     panic::set_hook(Box::new(move |info| {
         let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
         if prior_panic_count > 0 {
-            std::panic::resume_unwind(Box::new(()));
+            // Give the panic-ing thread time to write the panic file
+            loop {
+                std::thread::yield_now();
+            }
         }
 
-        let app_version = ZED_APP_VERSION
-            .or_else(|| platform.app_version().ok())
-            .map_or("dev".to_string(), |v| v.to_string());
-
         let thread = thread::current();
-        let thread = thread.name().unwrap_or("<unnamed>");
+        let thread_name = thread.name().unwrap_or("<unnamed>");
 
-        let payload = info.payload();
-        let payload = None
-            .or_else(|| payload.downcast_ref::<&str>().map(|s| s.to_string()))
-            .or_else(|| payload.downcast_ref::<String>().map(|s| s.clone()))
+        let payload = info
+            .payload()
+            .downcast_ref::<&str>()
+            .map(|s| s.to_string())
+            .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.clone()))
             .unwrap_or_else(|| "Box<Any>".to_string());
 
+        if *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
+            let location = info.location().unwrap();
+            let backtrace = Backtrace::new();
+            eprintln!(
+                "Thread {:?} panicked with {:?} at {}:{}:{}\n{:?}",
+                thread_name,
+                payload,
+                location.file(),
+                location.line(),
+                location.column(),
+                backtrace,
+            );
+            std::process::exit(-1);
+        }
+
+        let app_version = ZED_APP_VERSION
+            .or_else(|| platform.app_version().ok())
+            .map_or("dev".to_string(), |v| v.to_string());
+
         let backtrace = Backtrace::new();
         let mut backtrace = backtrace
             .frames()
@@ -447,7 +467,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>) {
         }
 
         let panic_data = Panic {
-            thread: thread.into(),
+            thread: thread_name.into(),
             payload: payload.into(),
             location_data: info.location().map(|location| LocationData {
                 file: location.file().into(),