Compare commits

...

3 Commits

Author SHA1 Message Date
Cole Miller
c6380d6c4e Fix 2024-12-13 10:23:19 -05:00
Cole Miller
35b2a3dd63 Fix 2024-12-13 09:52:31 -05:00
Cole Miller
512aad18e4 Introduce a mechanism for contextualizing panics 2024-12-13 09:24:05 -05:00
13 changed files with 139 additions and 16 deletions

10
Cargo.lock generated
View File

@@ -5346,6 +5346,7 @@ dependencies = [
"rand 0.8.5",
"raw-window-handle",
"refineable",
"reliability",
"resvg",
"schemars",
"seahash",
@@ -10261,6 +10262,13 @@ dependencies = [
"once_cell",
]
[[package]]
name = "reliability"
version = "0.1.0"
dependencies = [
"futures 0.3.31",
]
[[package]]
name = "remote"
version = "0.1.0"
@@ -10321,6 +10329,7 @@ dependencies = [
"project",
"proto",
"release_channel",
"reliability",
"remote",
"reqwest_client",
"rpc",
@@ -16041,6 +16050,7 @@ dependencies = [
"proto",
"recent_projects",
"release_channel",
"reliability",
"remote",
"repl",
"reqwest_client",

View File

@@ -92,6 +92,7 @@ members = [
"crates/refineable/derive_refineable",
"crates/release_channel",
"crates/remote",
"crates/reliability",
"crates/remote_server",
"crates/repl",
"crates/reqwest_client",
@@ -279,6 +280,7 @@ proto = { path = "crates/proto" }
recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" }
release_channel = { path = "crates/release_channel" }
reliability = { path = "crates/reliability" }
remote = { path = "crates/remote" }
remote_server = { path = "crates/remote_server" }
repl = { path = "crates/repl" }

View File

@@ -326,17 +326,23 @@ pub async fn post_panic(
let payload = slack::WebhookBody::new(|w| {
w.add_section(|s| s.text(slack::Text::markdown("Panic request".to_string())))
.add_section(|s| {
s.add_field(slack::Text::markdown(format!(
"*Version:*\n {} ",
panic.app_version
)))
.add_field({
slack::Text::markdown(format!(
"*OS:*\n{} {}",
panic.os_name,
panic.os_version.unwrap_or_default()
))
})
let s = s
.add_field(slack::Text::markdown(format!(
"*Version:*\n {} ",
panic.app_version
)))
.add_field({
slack::Text::markdown(format!(
"*OS:*\n{} {}",
panic.os_name,
panic.os_version.unwrap_or_default()
))
});
if let Some(context) = panic.context.as_ref() {
s.add_field(slack::Text::markdown(format!("*Context:* {context}")))
} else {
s
}
})
.add_rich_text(|r| r.add_preformatted(|p| p.add_text(backtrace_with_summary)))
});

View File

@@ -146,6 +146,7 @@ pathfinder_geometry = "0.5"
# Always used
flume = "0.11"
oo7 = "0.3.0"
reliability.workspace = true
# Used in both windowing options
ashpd = { workspace = true, optional = true }

View File

@@ -490,11 +490,19 @@ impl<P: LinuxClient + 'static> Platform for P {
.get("username")
.ok_or_else(|| anyhow!("Cannot find username in stored credentials"))?;
// oo7 panics if the retrieved secret can't be decrypted due to
// unexpected padding.
let secret = AssertUnwindSafe(item.secret())
.catch_unwind()
.await
.map_err(|_| anyhow!("oo7 panicked while trying to read credentials"))??;
// unexpected padding. Gather a little bit of extra information
// to understand what's up. Don't Debug-print here since that includes secrets!
let backend = match keyring {
oo7::Keyring::File { .. } => "file",
oo7::Keyring::DBus { .. } => "DBus",
};
let is_sandboxed = oo7::is_sandboxed().await;
let secret = reliability::hook_fn(move |_| {
format!("oo7 backend: {backend}, sandboxed: {is_sandboxed}")
})
.catch_unwind_future(item.secret())
.await
.map_err(|_| anyhow!("oo7 panicked while trying to read credentials"))??;
// we lose the zeroizing capabilities at this boundary,
// a current limitation GPUI's credentials api

View File

@@ -0,0 +1,17 @@
[package]
name = "reliability"
description = "Provide scoped context for panics"
edition = "2021"
version = "0.1.0"
publish = false
license = "GPL-3.0-or-later"
[lib]
path = "src/reliability.rs"
doctest = false
[lints]
workspace = true
[dependencies]
futures.workspace = true

View File

@@ -0,0 +1 @@
../../LICENSE-GPL

View File

@@ -0,0 +1,68 @@
use futures::FutureExt as _;
use std::{
cell::RefCell,
future::Future,
panic::{AssertUnwindSafe, PanicHookInfo},
};
thread_local! {
static GLOBAL_HOOK: RefCell<Option<Box<dyn ReliabilityHook + Send + Sync>>> = RefCell::new(None);
}
pub trait ReliabilityHook {
fn contextualize_panic(&self, info: &PanicHookInfo<'_>) -> String;
}
impl<F> ReliabilityHook for F
where
F: Fn(&PanicHookInfo<'_>) -> String,
{
fn contextualize_panic(&self, info: &PanicHookInfo<'_>) -> String {
self(info)
}
}
pub struct Guard(Option<Box<dyn ReliabilityHook + Send + Sync>>);
pub fn hook_fn(f: impl 'static + Send + Sync + Fn(&PanicHookInfo<'_>) -> String) -> Guard {
GLOBAL_HOOK.with_borrow_mut(|global| {
let old = std::mem::replace(global, Some(Box::new(f)));
Guard(old)
})
}
pub fn with_hook<R>(
f: impl FnOnce(&(dyn ReliabilityHook + Send + Sync + 'static)) -> R,
) -> Option<R> {
let result = GLOBAL_HOOK.with_borrow(|global| {
let global = &**global.as_ref()?;
Some(f(global))
})?;
Some(result)
}
impl Guard {
pub fn catch_unwind<R>(&self, f: impl FnOnce() -> R) -> std::thread::Result<R> {
std::panic::catch_unwind(AssertUnwindSafe(f))
}
pub async fn catch_unwind_future<R>(
&self,
f: impl Future<Output = R>,
) -> std::thread::Result<R> {
AssertUnwindSafe(async {
let _self = self;
f.await
})
.catch_unwind()
.await
}
}
impl Drop for Guard {
fn drop(&mut self) {
GLOBAL_HOOK.with_borrow_mut(|global| {
std::mem::swap(&mut self.0, global);
});
}
}

View File

@@ -47,6 +47,7 @@ paths = { workspace = true }
project.workspace = true
proto.workspace = true
release_channel.workspace = true
reliability.workspace = true
remote.workspace = true
reqwest_client.workspace = true
rpc.workspace = true

View File

@@ -148,6 +148,8 @@ fn init_panic_hook() {
(&backtrace).join("\n")
);
let context = reliability::with_hook(|hook| hook.contextualize_panic(info));
let panic_data = telemetry_events::Panic {
thread: thread_name.into(),
payload: payload.clone(),
@@ -165,6 +167,7 @@ fn init_panic_hook() {
architecture: env::consts::ARCH.into(),
panicked_on: Utc::now().timestamp_millis(),
backtrace,
context,
system_id: None, // Set on SSH client
installation_id: None, // Set on SSH client
session_id: "".to_string(), // Set on SSH client

View File

@@ -258,6 +258,8 @@ pub struct Panic {
#[serde(skip_serializing_if = "Option::is_none")]
pub location_data: Option<LocationData>,
pub backtrace: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<String>,
/// Zed version number
pub app_version: String,
/// Zed release channel (stable, preview, dev)

View File

@@ -89,6 +89,7 @@ project_symbols.workspace = true
proto.workspace = true
recent_projects.workspace = true
release_channel.workspace = true
reliability.workspace = true
remote.workspace = true
repl.workspace = true
reqwest_client.workspace = true

View File

@@ -90,6 +90,8 @@ pub fn init_panic_hook(
backtrace.drain(0..=ix);
}
let context = reliability::with_hook(|hook| hook.contextualize_panic(info));
let panic_data = telemetry_events::Panic {
thread: thread_name.into(),
payload,
@@ -104,6 +106,7 @@ pub fn init_panic_hook(
architecture: env::consts::ARCH.into(),
panicked_on: Utc::now().timestamp_millis(),
backtrace,
context,
system_id: system_id.clone(),
installation_id: installation_id.clone(),
session_id: session_id.clone(),