mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Crash reports: a window opened after startup no longer labels the run 'during startup' (#8776) (#9661)
* claim: #8776 * Crash reports: a window opened after startup no longer labels the run "during startup" (#8776) egui-wgpu reports "window surface" and "surface configure" for every native window, also the ones opened after startup, such as the agent chat in its own window. Each one overwrote the journal's "running" phase, so a later unclean exit was reported as "during startup (surface configure, N s after launch)". crash::startup_step records a startup step only until the app runs, and the app's egui-wgpu hook calls it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1145,12 +1145,13 @@ fn main() -> eframe::Result {
|
||||
}
|
||||
|
||||
/// egui-wgpu is about to start a step of getting the graphics device and the window's surface up:
|
||||
/// the crash journal keeps it, and the adapter once it is picked.
|
||||
/// the crash journal keeps it until the app runs (a window opened later goes through the same
|
||||
/// steps), and the adapter once it is picked.
|
||||
fn startup_step(step: &'static str, detail: &str) {
|
||||
if step == eframe::egui_wgpu::STEP_ADAPTER_CHOSEN {
|
||||
solidcraft_engine::crash::set_environment(&format!("Renderer: {detail}"));
|
||||
}
|
||||
solidcraft_engine::crash::phase(step);
|
||||
solidcraft_engine::crash::startup_step(step);
|
||||
}
|
||||
|
||||
/// The renderer settings: with `cpu`, the software adapter (WARP, lavapipe, llvmpipe). The
|
||||
|
||||
@@ -365,9 +365,22 @@ pub fn phase(step: &str) {
|
||||
j.write(&line);
|
||||
j.phase = Some(line);
|
||||
j.phase_step = Some(step.to_string());
|
||||
j.running |= step == RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
/// A step of getting a window's graphics up (egui-wgpu's `window surface`, `surface configure`,
|
||||
/// …): the [`phase`] until the app is [`RUNNING`], ignored after. A window opened later (the
|
||||
/// agent chat on its own, say) goes through the same steps, and must not make a crash an hour
|
||||
/// later read as a crash during startup (#8776).
|
||||
pub fn startup_step(step: &str) {
|
||||
let Some(s) = STATE.get() else { return };
|
||||
if s.journal.lock().is_ok_and(|j| j.running) {
|
||||
return;
|
||||
}
|
||||
phase(step);
|
||||
}
|
||||
|
||||
/// Where a native crash handler writes what it caught for this run: a small JSON file
|
||||
/// (`code`, `name`, `address`, `module`, `offset`, `detail`, `thread`, and from macOS
|
||||
/// `thread_name` and `frames`) and a minidump. The next
|
||||
@@ -622,6 +635,8 @@ struct Journal {
|
||||
/// The latest startup step's line and name, and the renderer's line (kept across rebases).
|
||||
phase: Option<String>,
|
||||
phase_step: Option<String>,
|
||||
/// The app reached [`RUNNING`] once: later startup steps are not startup ([`startup_step`]).
|
||||
running: bool,
|
||||
environment: Option<String>,
|
||||
}
|
||||
|
||||
@@ -642,6 +657,7 @@ impl Journal {
|
||||
running_at: None,
|
||||
phase: None,
|
||||
phase_step: None,
|
||||
running: false,
|
||||
environment: None,
|
||||
};
|
||||
j.rebase(&json!("new"), 0);
|
||||
@@ -889,6 +905,7 @@ fn unclean_exit(dir: &Path, build: &Build, journal: &Path) {
|
||||
running_at: None,
|
||||
phase: None,
|
||||
phase_step: None,
|
||||
running: false,
|
||||
environment: None,
|
||||
}),
|
||||
known: Mutex::new(list(dir).into_iter().filter_map(|s| sig_of(&s.id).map(|sig| (sig, (0, s.id)))).collect()),
|
||||
|
||||
@@ -50,6 +50,15 @@ fn child() {
|
||||
}
|
||||
std::process::abort();
|
||||
}
|
||||
// Up and running, then a second window's surface (the agent chat on its own), then dying.
|
||||
if mode == "late_surface" {
|
||||
startup_step("window surface");
|
||||
startup_step("surface configure");
|
||||
phase(RUNNING);
|
||||
startup_step("window surface");
|
||||
startup_step("surface configure");
|
||||
std::process::abort();
|
||||
}
|
||||
set_environment("Renderer: test");
|
||||
let mut s = Session { crash_journal: true, ..Default::default() };
|
||||
s.execute("solid.box", &json!({"length": 10, "width": 20, "height": 30})).unwrap();
|
||||
@@ -443,6 +452,21 @@ fn a_crash_signal_is_reported_with_its_thread_and_its_walk_up_the_stack() {
|
||||
let _ = fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_surface_after_startup_is_not_a_startup_crash() {
|
||||
let d = fresh_dir("late_surface");
|
||||
let (_, ok) = run_child(&d, "late_surface");
|
||||
assert!(!ok, "the child aborted");
|
||||
let (_, ok) = run_child(&d, "none");
|
||||
assert!(ok);
|
||||
let (s, v) = only_report(&d);
|
||||
assert_eq!(s.kind, "unclean_exit");
|
||||
assert!(!s.message.contains("during startup"), "{}", s.message);
|
||||
assert!(s.message.contains("between commands"), "{}", s.message);
|
||||
assert!(v.get("startup_phase").is_none(), "{v}");
|
||||
let _ = fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn startup_steps_survive_a_rebase_and_running_ends_startup() {
|
||||
let d = fresh_dir("rebase");
|
||||
|
||||
Reference in New Issue
Block a user