Merge refactor/qbz-app-boot-watchdog: boot watchdog
cx
This commit is contained in:
3 files changed
+427
-211
No files matched your search
@@ -0,0 +1,413 @@
|
||||
//! Framework-agnostic boot watchdog decision logic.
|
||||
//!
|
||||
//! The Tauri shell owns marker-file IO, settings persistence, and startup env
|
||||
//! mutation. This module owns the pure crash-recovery state transition that can
|
||||
//! be reused by any future native shell.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Snapshot of the risky settings attempted in a boot. Compared against the
|
||||
/// previous boot's snapshot to decide which setting "caused" a crash.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct BootAttempt {
|
||||
pub hardware_acceleration: bool,
|
||||
pub force_dmabuf: bool,
|
||||
pub preferred_gpu: String,
|
||||
pub force_x11: bool,
|
||||
}
|
||||
|
||||
/// Sticky flags written when a setting was auto-reverted after a crash.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct CrashFlags {
|
||||
/// Last boot crashed with hardware_acceleration ON; we reverted it.
|
||||
pub hardware_acceleration_disabled: bool,
|
||||
pub hardware_acceleration_consecutive_failures: u8,
|
||||
/// Same for force_dmabuf.
|
||||
pub force_dmabuf_disabled: bool,
|
||||
pub force_dmabuf_consecutive_failures: u8,
|
||||
/// Same for preferred_gpu (reset to "auto").
|
||||
pub preferred_gpu_disabled: bool,
|
||||
pub preferred_gpu_consecutive_failures: u8,
|
||||
/// Lockout - set when consecutive_failures >= 2 for a given knob.
|
||||
pub hardware_acceleration_locked: bool,
|
||||
pub force_dmabuf_locked: bool,
|
||||
pub preferred_gpu_locked: bool,
|
||||
}
|
||||
|
||||
/// Resolved settings the host shell should actually use for this boot.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct WatchdogResolution {
|
||||
pub hardware_acceleration: bool,
|
||||
pub force_dmabuf: bool,
|
||||
pub preferred_gpu: String,
|
||||
/// Reverted-in-this-boot messages so the UI knows which setting was
|
||||
/// just rolled back. Persisted in CrashFlags as well.
|
||||
pub recovery_messages: Vec<String>,
|
||||
}
|
||||
|
||||
/// Pure output for a boot watchdog decision. The host adapter decides how to
|
||||
/// persist marker files and settings writes from this value.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BootWatchdogDecision {
|
||||
pub resolved_attempt: BootAttempt,
|
||||
pub crash_flags: CrashFlags,
|
||||
pub recovery_messages: Vec<String>,
|
||||
pub should_persist_reverts: bool,
|
||||
}
|
||||
|
||||
impl BootWatchdogDecision {
|
||||
pub fn resolution(&self) -> WatchdogResolution {
|
||||
WatchdogResolution {
|
||||
hardware_acceleration: self.resolved_attempt.hardware_acceleration,
|
||||
force_dmabuf: self.resolved_attempt.force_dmabuf,
|
||||
preferred_gpu: self.resolved_attempt.preferred_gpu.clone(),
|
||||
recovery_messages: self.recovery_messages.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_boot_attempt(
|
||||
intended: BootAttempt,
|
||||
pending: Option<BootAttempt>,
|
||||
mut flags: CrashFlags,
|
||||
) -> BootWatchdogDecision {
|
||||
let mut resolved = intended.clone();
|
||||
let mut messages: Vec<String> = Vec::new();
|
||||
let mut any_revert = false;
|
||||
|
||||
if let Some(prev) = pending {
|
||||
if prev.hardware_acceleration && resolved.hardware_acceleration {
|
||||
flags.hardware_acceleration_consecutive_failures = flags
|
||||
.hardware_acceleration_consecutive_failures
|
||||
.saturating_add(1);
|
||||
resolved.hardware_acceleration = false;
|
||||
flags.hardware_acceleration_disabled = true;
|
||||
if flags.hardware_acceleration_consecutive_failures >= 2 {
|
||||
flags.hardware_acceleration_locked = true;
|
||||
}
|
||||
messages.push(
|
||||
"Hardware acceleration was disabled because the previous launch crashed during \
|
||||
graphics init."
|
||||
.to_string(),
|
||||
);
|
||||
any_revert = true;
|
||||
}
|
||||
|
||||
if prev.force_dmabuf && resolved.force_dmabuf {
|
||||
flags.force_dmabuf_consecutive_failures =
|
||||
flags.force_dmabuf_consecutive_failures.saturating_add(1);
|
||||
resolved.force_dmabuf = false;
|
||||
flags.force_dmabuf_disabled = true;
|
||||
if flags.force_dmabuf_consecutive_failures >= 2 {
|
||||
flags.force_dmabuf_locked = true;
|
||||
}
|
||||
messages.push(
|
||||
"DMA-BUF renderer was disabled because the previous launch crashed during \
|
||||
graphics init."
|
||||
.to_string(),
|
||||
);
|
||||
any_revert = true;
|
||||
}
|
||||
|
||||
if prev.preferred_gpu == resolved.preferred_gpu && resolved.preferred_gpu != "auto" {
|
||||
flags.preferred_gpu_consecutive_failures =
|
||||
flags.preferred_gpu_consecutive_failures.saturating_add(1);
|
||||
resolved.preferred_gpu = "auto".to_string();
|
||||
flags.preferred_gpu_disabled = true;
|
||||
if flags.preferred_gpu_consecutive_failures >= 2 {
|
||||
flags.preferred_gpu_locked = true;
|
||||
}
|
||||
messages.push(format!(
|
||||
"Rendering GPU preference was reset to Auto because pinning to {:?} crashed the \
|
||||
previous launch.",
|
||||
prev.preferred_gpu
|
||||
));
|
||||
any_revert = true;
|
||||
}
|
||||
|
||||
if any_revert {
|
||||
if resolved.hardware_acceleration {
|
||||
resolved.hardware_acceleration = false;
|
||||
flags.hardware_acceleration_disabled = true;
|
||||
messages.push(
|
||||
"Hardware acceleration was also disabled as a safety fallback to land in \
|
||||
CPU mode."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if resolved.force_dmabuf {
|
||||
resolved.force_dmabuf = false;
|
||||
flags.force_dmabuf_disabled = true;
|
||||
}
|
||||
if resolved.preferred_gpu != "auto" {
|
||||
resolved.preferred_gpu = "auto".to_string();
|
||||
flags.preferred_gpu_disabled = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !flags.hardware_acceleration_locked {
|
||||
flags.hardware_acceleration_disabled = false;
|
||||
flags.hardware_acceleration_consecutive_failures = 0;
|
||||
}
|
||||
if !flags.force_dmabuf_locked {
|
||||
flags.force_dmabuf_disabled = false;
|
||||
flags.force_dmabuf_consecutive_failures = 0;
|
||||
}
|
||||
if !flags.preferred_gpu_locked {
|
||||
flags.preferred_gpu_disabled = false;
|
||||
flags.preferred_gpu_consecutive_failures = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let should_persist_reverts = resolved != intended;
|
||||
BootWatchdogDecision {
|
||||
resolved_attempt: resolved,
|
||||
crash_flags: flags,
|
||||
recovery_messages: messages,
|
||||
should_persist_reverts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_boot_success_flags(attempt: &BootAttempt, mut flags: CrashFlags) -> CrashFlags {
|
||||
if attempt.hardware_acceleration {
|
||||
flags.hardware_acceleration_consecutive_failures = 0;
|
||||
flags.hardware_acceleration_disabled = false;
|
||||
flags.hardware_acceleration_locked = false;
|
||||
}
|
||||
if attempt.force_dmabuf {
|
||||
flags.force_dmabuf_consecutive_failures = 0;
|
||||
flags.force_dmabuf_disabled = false;
|
||||
flags.force_dmabuf_locked = false;
|
||||
}
|
||||
if attempt.preferred_gpu != "auto" {
|
||||
flags.preferred_gpu_consecutive_failures = 0;
|
||||
flags.preferred_gpu_disabled = false;
|
||||
flags.preferred_gpu_locked = false;
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
pub fn clear_crash_flag_value(mut flags: CrashFlags, flag: &str) -> Option<CrashFlags> {
|
||||
match flag {
|
||||
"hardware_acceleration" => {
|
||||
flags.hardware_acceleration_disabled = false;
|
||||
flags.hardware_acceleration_consecutive_failures = 0;
|
||||
flags.hardware_acceleration_locked = false;
|
||||
}
|
||||
"force_dmabuf" => {
|
||||
flags.force_dmabuf_disabled = false;
|
||||
flags.force_dmabuf_consecutive_failures = 0;
|
||||
flags.force_dmabuf_locked = false;
|
||||
}
|
||||
"preferred_gpu" => {
|
||||
flags.preferred_gpu_disabled = false;
|
||||
flags.preferred_gpu_consecutive_failures = 0;
|
||||
flags.preferred_gpu_locked = false;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(flags)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn attempt() -> BootAttempt {
|
||||
BootAttempt {
|
||||
hardware_acceleration: true,
|
||||
force_dmabuf: true,
|
||||
preferred_gpu: "auto".to_string(),
|
||||
force_x11: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_pending_returns_intended_and_clears_non_locked_flags() {
|
||||
let flags = CrashFlags {
|
||||
hardware_acceleration_disabled: true,
|
||||
hardware_acceleration_consecutive_failures: 1,
|
||||
force_dmabuf_disabled: true,
|
||||
force_dmabuf_consecutive_failures: 1,
|
||||
preferred_gpu_disabled: true,
|
||||
preferred_gpu_consecutive_failures: 1,
|
||||
..CrashFlags::default()
|
||||
};
|
||||
|
||||
let decision = resolve_boot_attempt(attempt(), None, flags);
|
||||
|
||||
assert_eq!(decision.resolved_attempt, attempt());
|
||||
assert!(!decision.should_persist_reverts);
|
||||
assert_eq!(decision.crash_flags, CrashFlags::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_pending_preserves_locked_flags() {
|
||||
let flags = CrashFlags {
|
||||
hardware_acceleration_disabled: true,
|
||||
hardware_acceleration_consecutive_failures: 2,
|
||||
hardware_acceleration_locked: true,
|
||||
..CrashFlags::default()
|
||||
};
|
||||
|
||||
let decision = resolve_boot_attempt(attempt(), None, flags);
|
||||
|
||||
assert!(decision.crash_flags.hardware_acceleration_disabled);
|
||||
assert_eq!(
|
||||
decision
|
||||
.crash_flags
|
||||
.hardware_acceleration_consecutive_failures,
|
||||
2
|
||||
);
|
||||
assert!(decision.crash_flags.hardware_acceleration_locked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_hardware_acceleration_crash_disables_hardware_acceleration() {
|
||||
let decision = resolve_boot_attempt(attempt(), Some(attempt()), CrashFlags::default());
|
||||
|
||||
assert!(!decision.resolved_attempt.hardware_acceleration);
|
||||
assert!(!decision.resolved_attempt.force_dmabuf);
|
||||
assert_eq!(decision.resolved_attempt.preferred_gpu, "auto");
|
||||
assert!(decision.crash_flags.hardware_acceleration_disabled);
|
||||
assert_eq!(
|
||||
decision
|
||||
.crash_flags
|
||||
.hardware_acceleration_consecutive_failures,
|
||||
1
|
||||
);
|
||||
assert!(decision.should_persist_reverts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_dmabuf_crash_disables_dmabuf() {
|
||||
let mut intended = attempt();
|
||||
intended.hardware_acceleration = false;
|
||||
let pending = intended.clone();
|
||||
|
||||
let decision = resolve_boot_attempt(intended, Some(pending), CrashFlags::default());
|
||||
|
||||
assert!(!decision.resolved_attempt.force_dmabuf);
|
||||
assert!(decision.crash_flags.force_dmabuf_disabled);
|
||||
assert_eq!(decision.crash_flags.force_dmabuf_consecutive_failures, 1);
|
||||
assert!(decision.should_persist_reverts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_same_preferred_gpu_resets_to_auto() {
|
||||
let mut intended = attempt();
|
||||
intended.hardware_acceleration = false;
|
||||
intended.force_dmabuf = false;
|
||||
intended.preferred_gpu = "0000:01:00.0".to_string();
|
||||
let pending = intended.clone();
|
||||
|
||||
let decision = resolve_boot_attempt(intended, Some(pending), CrashFlags::default());
|
||||
|
||||
assert_eq!(decision.resolved_attempt.preferred_gpu, "auto");
|
||||
assert!(decision.crash_flags.preferred_gpu_disabled);
|
||||
assert_eq!(decision.crash_flags.preferred_gpu_consecutive_failures, 1);
|
||||
assert!(decision.should_persist_reverts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_different_preferred_gpu_does_not_blame_gpu_choice() {
|
||||
let mut intended = attempt();
|
||||
intended.hardware_acceleration = false;
|
||||
intended.force_dmabuf = false;
|
||||
intended.preferred_gpu = "0000:02:00.0".to_string();
|
||||
let mut pending = intended.clone();
|
||||
pending.preferred_gpu = "0000:01:00.0".to_string();
|
||||
|
||||
let decision = resolve_boot_attempt(intended.clone(), Some(pending), CrashFlags::default());
|
||||
|
||||
assert_eq!(decision.resolved_attempt, intended);
|
||||
assert!(!decision.crash_flags.preferred_gpu_disabled);
|
||||
assert!(!decision.should_persist_reverts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_failure_locks_setting() {
|
||||
let flags = CrashFlags {
|
||||
hardware_acceleration_consecutive_failures: 1,
|
||||
..CrashFlags::default()
|
||||
};
|
||||
|
||||
let decision = resolve_boot_attempt(attempt(), Some(attempt()), flags);
|
||||
|
||||
assert_eq!(
|
||||
decision
|
||||
.crash_flags
|
||||
.hardware_acceleration_consecutive_failures,
|
||||
2
|
||||
);
|
||||
assert!(decision.crash_flags.hardware_acceleration_locked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn success_with_enabled_knobs_clears_matching_flags() {
|
||||
let flags = CrashFlags {
|
||||
hardware_acceleration_disabled: true,
|
||||
hardware_acceleration_consecutive_failures: 2,
|
||||
hardware_acceleration_locked: true,
|
||||
force_dmabuf_disabled: true,
|
||||
force_dmabuf_consecutive_failures: 2,
|
||||
force_dmabuf_locked: true,
|
||||
preferred_gpu_disabled: true,
|
||||
preferred_gpu_consecutive_failures: 2,
|
||||
preferred_gpu_locked: true,
|
||||
};
|
||||
let mut boot = attempt();
|
||||
boot.preferred_gpu = "0000:01:00.0".to_string();
|
||||
|
||||
let updated = mark_boot_success_flags(&boot, flags);
|
||||
|
||||
assert_eq!(updated, CrashFlags::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn success_with_cpu_fallback_does_not_clear_disabled_flags() {
|
||||
let flags = CrashFlags {
|
||||
hardware_acceleration_disabled: true,
|
||||
hardware_acceleration_consecutive_failures: 1,
|
||||
force_dmabuf_disabled: true,
|
||||
force_dmabuf_consecutive_failures: 1,
|
||||
preferred_gpu_disabled: true,
|
||||
preferred_gpu_consecutive_failures: 1,
|
||||
..CrashFlags::default()
|
||||
};
|
||||
let boot = BootAttempt {
|
||||
hardware_acceleration: false,
|
||||
force_dmabuf: false,
|
||||
preferred_gpu: "auto".to_string(),
|
||||
force_x11: false,
|
||||
};
|
||||
|
||||
let updated = mark_boot_success_flags(&boot, flags.clone());
|
||||
|
||||
assert_eq!(updated, flags);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_known_flag_resets_disabled_counter_and_lock() {
|
||||
let flags = CrashFlags {
|
||||
force_dmabuf_disabled: true,
|
||||
force_dmabuf_consecutive_failures: 2,
|
||||
force_dmabuf_locked: true,
|
||||
..CrashFlags::default()
|
||||
};
|
||||
|
||||
let updated = clear_crash_flag_value(flags, "force_dmabuf").expect("known flag");
|
||||
|
||||
assert!(!updated.force_dmabuf_disabled);
|
||||
assert_eq!(updated.force_dmabuf_consecutive_failures, 0);
|
||||
assert!(!updated.force_dmabuf_locked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_unknown_flag_is_noop() {
|
||||
assert_eq!(
|
||||
clear_crash_flag_value(CrashFlags::default(), "force_x11"),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod boot_watchdog;
|
||||
pub mod graphics_autoconfig;
|
||||
pub mod graphics_detection;
|
||||
pub mod runtime;
|
||||
|
||||
+13
-211
@@ -22,7 +22,10 @@
|
||||
//! state: the setting stays disabled until the user explicitly clears
|
||||
//! the flag from Settings. Prevents an infinite retry loop.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use qbz_app::boot_watchdog::{
|
||||
clear_crash_flag_value, mark_boot_success_flags, resolve_boot_attempt,
|
||||
};
|
||||
pub use qbz_app::boot_watchdog::{BootAttempt, CrashFlags, WatchdogResolution};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -31,52 +34,6 @@ const PENDING_FILE: &str = "pending.json";
|
||||
const LAST_GOOD_FILE: &str = "last-good.json";
|
||||
const CRASH_FLAGS_FILE: &str = "crash-flags.json";
|
||||
|
||||
/// Snapshot of the risky settings attempted in a boot. Compared against
|
||||
/// the previous boot's snapshot to decide which setting "caused" a crash.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct BootAttempt {
|
||||
pub hardware_acceleration: bool,
|
||||
pub force_dmabuf: bool,
|
||||
pub preferred_gpu: String,
|
||||
pub force_x11: bool,
|
||||
}
|
||||
|
||||
/// Sticky flags written when a setting was auto-reverted after a crash.
|
||||
/// The UI reads these to render the recovery banner. Frontend can also
|
||||
/// clear them via `v2_clear_crash_recovery_flag`.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct CrashFlags {
|
||||
/// Last boot crashed with hardware_acceleration ON; we reverted it.
|
||||
pub hardware_acceleration_disabled: bool,
|
||||
pub hardware_acceleration_consecutive_failures: u8,
|
||||
/// Same for force_dmabuf.
|
||||
pub force_dmabuf_disabled: bool,
|
||||
pub force_dmabuf_consecutive_failures: u8,
|
||||
/// Same for preferred_gpu (reset to "auto").
|
||||
pub preferred_gpu_disabled: bool,
|
||||
pub preferred_gpu_consecutive_failures: u8,
|
||||
/// Lockout — set when consecutive_failures >= 2 for a given knob.
|
||||
/// The runtime continues to revert; the UI shows a stronger
|
||||
/// "disabled due to repeated crashes" message and gates re-enable
|
||||
/// behind an explicit clear action.
|
||||
pub hardware_acceleration_locked: bool,
|
||||
pub force_dmabuf_locked: bool,
|
||||
pub preferred_gpu_locked: bool,
|
||||
}
|
||||
|
||||
/// Result of `before_webkit_init`. The caller (main.rs) replaces its
|
||||
/// own variables for hardware_accel / force_dmabuf / preferred_gpu
|
||||
/// with these values before proceeding to apply env vars.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WatchdogResolution {
|
||||
pub hardware_acceleration: bool,
|
||||
pub force_dmabuf: bool,
|
||||
pub preferred_gpu: String,
|
||||
/// Reverted-in-this-boot flags so the UI knows which setting was
|
||||
/// just rolled back. Persisted in CrashFlags as well.
|
||||
pub recovery_messages: Vec<String>,
|
||||
}
|
||||
|
||||
fn state_dir() -> Option<PathBuf> {
|
||||
let base = dirs::data_dir()?.join("qbz").join(STATE_DIR);
|
||||
fs::create_dir_all(&base).ok()?;
|
||||
@@ -141,135 +98,14 @@ fn write_crash_flags(flags: &CrashFlags) {
|
||||
/// Also writes the new pending marker for THIS boot so a crash before
|
||||
/// the frontend signals success is detectable on the next launch.
|
||||
pub fn before_webkit_init(intended: BootAttempt) -> WatchdogResolution {
|
||||
let mut flags = read_crash_flags();
|
||||
let mut resolved = intended.clone();
|
||||
let mut messages: Vec<String> = Vec::new();
|
||||
|
||||
// If a previous boot left a pending marker, it crashed before
|
||||
// first paint. Compare what it tried against what we'd try now,
|
||||
// and revert the field(s) that look like the cause.
|
||||
let mut any_revert = false;
|
||||
if let Some(prev) = read_pending() {
|
||||
// Hardware acceleration: if previous tried ON and we're also
|
||||
// trying ON, blame this knob and revert.
|
||||
if prev.hardware_acceleration && resolved.hardware_acceleration {
|
||||
flags.hardware_acceleration_consecutive_failures =
|
||||
flags.hardware_acceleration_consecutive_failures.saturating_add(1);
|
||||
resolved.hardware_acceleration = false;
|
||||
flags.hardware_acceleration_disabled = true;
|
||||
if flags.hardware_acceleration_consecutive_failures >= 2 {
|
||||
flags.hardware_acceleration_locked = true;
|
||||
}
|
||||
messages.push(
|
||||
"Hardware acceleration was disabled because the previous launch crashed during \
|
||||
graphics init."
|
||||
.to_string(),
|
||||
);
|
||||
any_revert = true;
|
||||
}
|
||||
// Force DMA-BUF: same logic.
|
||||
if prev.force_dmabuf && resolved.force_dmabuf {
|
||||
flags.force_dmabuf_consecutive_failures =
|
||||
flags.force_dmabuf_consecutive_failures.saturating_add(1);
|
||||
resolved.force_dmabuf = false;
|
||||
flags.force_dmabuf_disabled = true;
|
||||
if flags.force_dmabuf_consecutive_failures >= 2 {
|
||||
flags.force_dmabuf_locked = true;
|
||||
}
|
||||
messages.push(
|
||||
"DMA-BUF renderer was disabled because the previous launch crashed during \
|
||||
graphics init."
|
||||
.to_string(),
|
||||
);
|
||||
any_revert = true;
|
||||
}
|
||||
// Preferred GPU: only revert when the SAME non-auto choice
|
||||
// was attempted. Switching from one explicit GPU to another
|
||||
// shouldn't inherit a crash flag from the prior choice.
|
||||
if prev.preferred_gpu == resolved.preferred_gpu && resolved.preferred_gpu != "auto" {
|
||||
flags.preferred_gpu_consecutive_failures =
|
||||
flags.preferred_gpu_consecutive_failures.saturating_add(1);
|
||||
resolved.preferred_gpu = "auto".to_string();
|
||||
flags.preferred_gpu_disabled = true;
|
||||
if flags.preferred_gpu_consecutive_failures >= 2 {
|
||||
flags.preferred_gpu_locked = true;
|
||||
}
|
||||
messages.push(format!(
|
||||
"Rendering GPU preference was reset to Auto because pinning to {:?} crashed the \
|
||||
previous launch.",
|
||||
prev.preferred_gpu
|
||||
));
|
||||
any_revert = true;
|
||||
}
|
||||
|
||||
// Full CPU fallback after ANY revert. The single-knob revert
|
||||
// above narrows down the *cause* for the UI banner, but the
|
||||
// safest landing pad for a boot that just crashed in graphics
|
||||
// init is full CPU mode (hardware_acceleration off, DMA-BUF
|
||||
// off, preferred_gpu cleared to auto). A partial revert can
|
||||
// still hit the next crash if the failure was a stack-wide
|
||||
// issue (driver bug, missing extension) and only one symptom
|
||||
// was the visible one. Going all-the-way-CPU guarantees the
|
||||
// user always reaches a paintable window after a recovery —
|
||||
// they can selectively re-enable from the Settings banner.
|
||||
if any_revert {
|
||||
if resolved.hardware_acceleration {
|
||||
resolved.hardware_acceleration = false;
|
||||
flags.hardware_acceleration_disabled = true;
|
||||
messages.push(
|
||||
"Hardware acceleration was also disabled as a safety fallback to land in \
|
||||
CPU mode."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if resolved.force_dmabuf {
|
||||
resolved.force_dmabuf = false;
|
||||
flags.force_dmabuf_disabled = true;
|
||||
}
|
||||
if resolved.preferred_gpu != "auto" {
|
||||
resolved.preferred_gpu = "auto".to_string();
|
||||
flags.preferred_gpu_disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Persist the revert decisions back to graphics_settings.db so
|
||||
// the user sees the new state in the UI and the next boot
|
||||
// doesn't try the same setting again.
|
||||
persist_reverts_to_db(&resolved, &intended);
|
||||
|
||||
// Clear consecutive-failure counters for any knob that was
|
||||
// attempted AND succeeded (we infer "succeeded" from "this
|
||||
// boot's input matches last-good"). Handled in
|
||||
// mark_boot_succeeded for paths that actually paint.
|
||||
} else {
|
||||
// No pending marker → previous boot either succeeded or never
|
||||
// ran the watchdog. Reset disabled flags to non-sticky values
|
||||
// so the UI doesn't keep showing a banner across launches
|
||||
// where everything's fine. Locked flags STAY sticky — those
|
||||
// require explicit user clear.
|
||||
if !flags.hardware_acceleration_locked {
|
||||
flags.hardware_acceleration_disabled = false;
|
||||
flags.hardware_acceleration_consecutive_failures = 0;
|
||||
}
|
||||
if !flags.force_dmabuf_locked {
|
||||
flags.force_dmabuf_disabled = false;
|
||||
flags.force_dmabuf_consecutive_failures = 0;
|
||||
}
|
||||
if !flags.preferred_gpu_locked {
|
||||
flags.preferred_gpu_disabled = false;
|
||||
flags.preferred_gpu_consecutive_failures = 0;
|
||||
}
|
||||
let decision = resolve_boot_attempt(intended.clone(), read_pending(), read_crash_flags());
|
||||
if decision.should_persist_reverts {
|
||||
persist_reverts_to_db(&decision.resolved_attempt, &intended);
|
||||
}
|
||||
|
||||
write_crash_flags(&flags);
|
||||
write_pending(&resolved);
|
||||
|
||||
WatchdogResolution {
|
||||
hardware_acceleration: resolved.hardware_acceleration,
|
||||
force_dmabuf: resolved.force_dmabuf,
|
||||
preferred_gpu: resolved.preferred_gpu,
|
||||
recovery_messages: messages,
|
||||
}
|
||||
write_crash_flags(&decision.crash_flags);
|
||||
write_pending(&decision.resolved_attempt);
|
||||
decision.resolution()
|
||||
}
|
||||
|
||||
/// Called from a Tauri command after the frontend signals first paint.
|
||||
@@ -284,24 +120,7 @@ pub fn mark_boot_succeeded() {
|
||||
clear_pending();
|
||||
write_last_good(&attempt);
|
||||
|
||||
let mut flags = read_crash_flags();
|
||||
// Successful paint with HW accel ON resets that knob's failure
|
||||
// streak entirely (including lockout — the issue resolved).
|
||||
if attempt.hardware_acceleration {
|
||||
flags.hardware_acceleration_consecutive_failures = 0;
|
||||
flags.hardware_acceleration_disabled = false;
|
||||
flags.hardware_acceleration_locked = false;
|
||||
}
|
||||
if attempt.force_dmabuf {
|
||||
flags.force_dmabuf_consecutive_failures = 0;
|
||||
flags.force_dmabuf_disabled = false;
|
||||
flags.force_dmabuf_locked = false;
|
||||
}
|
||||
if attempt.preferred_gpu != "auto" {
|
||||
flags.preferred_gpu_consecutive_failures = 0;
|
||||
flags.preferred_gpu_disabled = false;
|
||||
flags.preferred_gpu_locked = false;
|
||||
}
|
||||
let flags = mark_boot_success_flags(&attempt, read_crash_flags());
|
||||
write_crash_flags(&flags);
|
||||
}
|
||||
|
||||
@@ -314,26 +133,9 @@ pub fn get_crash_flags() -> CrashFlags {
|
||||
/// the user clicks "Try again" / "I want to re-enable this" on the
|
||||
/// recovery banner. Unknown names are no-ops.
|
||||
pub fn clear_crash_flag(flag: &str) {
|
||||
let mut flags = read_crash_flags();
|
||||
match flag {
|
||||
"hardware_acceleration" => {
|
||||
flags.hardware_acceleration_disabled = false;
|
||||
flags.hardware_acceleration_consecutive_failures = 0;
|
||||
flags.hardware_acceleration_locked = false;
|
||||
}
|
||||
"force_dmabuf" => {
|
||||
flags.force_dmabuf_disabled = false;
|
||||
flags.force_dmabuf_consecutive_failures = 0;
|
||||
flags.force_dmabuf_locked = false;
|
||||
}
|
||||
"preferred_gpu" => {
|
||||
flags.preferred_gpu_disabled = false;
|
||||
flags.preferred_gpu_consecutive_failures = 0;
|
||||
flags.preferred_gpu_locked = false;
|
||||
}
|
||||
_ => return,
|
||||
if let Some(flags) = clear_crash_flag_value(read_crash_flags(), flag) {
|
||||
write_crash_flags(&flags);
|
||||
}
|
||||
write_crash_flags(&flags);
|
||||
}
|
||||
|
||||
/// Best-effort write of reverted values back to graphics_settings.db
|
||||
|
||||
Reference in new issue
Block a user