diff --git a/src/gpu_backend.rs b/src/gpu_backend.rs index ad16e13ab..8b5141cd1 100644 --- a/src/gpu_backend.rs +++ b/src/gpu_backend.rs @@ -357,7 +357,12 @@ fn crash_report_blames_the_device(sentinel: &str) -> bool { else { return false; }; - crate::sys::crash_log::report_for_pid(pid) + let armed = sentinel + .lines() + .find_map(|line| line.strip_prefix("t=")) + .and_then(|value| value.trim().parse::().ok()) + .unwrap_or(0); + crate::sys::crash_log::report_for_pid(pid, armed) .is_some_and(|report| crate::sys::crash_log::is_device_failure(&report)) } @@ -1129,7 +1134,14 @@ mod tests { let reports = crate::sys::crash_log::directory().expect("a config directory"); let _ = std::fs::create_dir_all(&reports); - let report = reports.join(format!("crash-1-{}.log", std::process::id())); + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + // A report older than the run, left by another process that once + // had this id, blames nobody. + let stale = reports.join(format!("crash-1-{}.log", std::process::id())); + let report = reports.join(format!("crash-{}-{}.log", now + 1, std::process::id())); arm_sentinel("dx12"); mark_sentinel_survived(); @@ -1138,6 +1150,16 @@ mod tests { crashed_backends().is_empty(), "with no crash report, a survived run blames nobody" ); + std::fs::write( + &stale, + crate::sys::crash_log::report_from("Out of Memory", "wgpu-29.0.4/src/lib.rs:1:1", 1), + ) + .unwrap(); + assert!( + crashed_backends().is_empty(), + "a report from before the run belongs to another process" + ); + let _ = std::fs::remove_file(&stale); // The run left a report, and it is the device's own failure. std::fs::write( diff --git a/src/sys.rs b/src/sys.rs index 52d27561e..9c0a9c757 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -435,17 +435,22 @@ pub mod crash_log { /// a process id from somewhere else — the GPU crash sentinel does — can /// ask what killed that exact run instead of guessing from the newest /// file, which on a busy machine may belong to a different one. - pub fn report_for_pid(pid: u32) -> Option { + /// + /// Process ids are reused, so only a report written at or after + /// `not_before` (Unix seconds — when that run started) counts: an old + /// report from an unrelated run that once had the same id does not. + pub fn report_for_pid(pid: u32, not_before: u64) -> Option { let directory = directory()?; let suffix = format!("-{pid}.log"); let entry = std::fs::read_dir(directory) .ok()? .flatten() .find(|entry| { - entry - .file_name() - .to_string_lossy() - .ends_with(suffix.as_str()) + let name = entry.file_name().to_string_lossy().into_owned(); + name.strip_prefix("crash-") + .and_then(|rest| rest.strip_suffix(suffix.as_str())) + .and_then(|when| when.parse::().ok()) + .is_some_and(|when| when >= not_before) })?; std::fs::read_to_string(entry.path()).ok() } @@ -468,9 +473,11 @@ pub mod crash_log { } // The message wgpu panics with names the call, and the location names // wgpu itself; either alone is enough, since a stripped build can - // leave the message terse. + // leave the message terse. Plain words like "buffer" or "surface" + // are not: drawing code and the modelling kernel panic with those + // too, and that is no reason to give up a working backend. let haystack = format!("{panic_line} {at_line}").to_ascii_lowercase(); - ["wgpu", "out of memory", "buffer", "surface", "device lost"] + ["wgpu", "out of memory", "device lost", "validation error"] .iter() .any(|needle| haystack.contains(needle)) }