Implement debug crash handler dialog (#139)
This commit is contained in:
parent
4b49046999
commit
9672858b48
|
@ -14,6 +14,7 @@
|
|||
- Adds `zng::app::crash_handler`.
|
||||
- Can be used to easily implement crash reporting, stacktrace and minidump
|
||||
collection, app restart on crash.
|
||||
- Call `zng::app::crash_handler::init_debug()` to quickly setup panic and minidump collection.
|
||||
* Fix view-process kill by user not working after respawn.
|
||||
* Fix view-process assuming any signal kill was requested by the user.
|
||||
* Fix potential issue retrieving current_exe trough symbolic links.
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
fmt,
|
||||
io::{BufRead, BufReader},
|
||||
path::{Path, PathBuf},
|
||||
time::{Duration, SystemTime},
|
||||
time::SystemTime,
|
||||
};
|
||||
use zng_layout::unit::TimeUnits as _;
|
||||
|
||||
|
@ -184,18 +184,12 @@ pub struct CrashError {
|
|||
/// Minidump file.
|
||||
pub minidump: Option<PathBuf>,
|
||||
}
|
||||
/// Alternate mode `{:#}` prints plain stdout and stderr (no ANSI escape sequences).
|
||||
impl fmt::Display for CrashError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"timestamp: {}",
|
||||
self.timestamp
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
.as_secs()
|
||||
)?;
|
||||
writeln!(f, "timestamp: {}", self.unix_time())?;
|
||||
if let Some(c) = self.code {
|
||||
writeln!(f, "exit code: {c:#x}")?
|
||||
writeln!(f, "exit code: {c:#X}")?
|
||||
}
|
||||
if let Some(c) = self.signal {
|
||||
writeln!(f, "exit signal: {c}")?
|
||||
|
@ -203,7 +197,11 @@ impl fmt::Display for CrashError {
|
|||
if let Some(p) = self.minidump.as_ref() {
|
||||
writeln!(f, "minidump: {}", p.display())?
|
||||
}
|
||||
write!(f, "\nSTDOUT:\n{}\nSTDERR:\n{}\n", self.stdout, self.stderr)
|
||||
if f.alternate() {
|
||||
write!(f, "\nSTDOUT:\n{}\nSTDERR:\n{}\n", self.stdout_plain(), self.stderr_plain())
|
||||
} else {
|
||||
write!(f, "\nSTDOUT:\n{}\nSTDERR:\n{}\n", self.stdout, self.stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CrashError {
|
||||
|
@ -233,13 +231,56 @@ impl CrashError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Seconds since Unix epoch.
|
||||
pub fn unix_time(&self) -> u64 {
|
||||
self.timestamp.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs()
|
||||
}
|
||||
|
||||
/// Gets if `stdout` does not contain any ANSI scape sequences.
|
||||
pub fn is_stdout_plain(&self) -> bool {
|
||||
!self.stdout.contains(CSI)
|
||||
}
|
||||
|
||||
/// Gets if `stderr` does not contain any ANSI scape sequences.
|
||||
pub fn is_stderr_plain(&self) -> bool {
|
||||
!self.stderr.contains(CSI)
|
||||
}
|
||||
|
||||
/// Get `stdout` without any ANSI escape sequences (CSI).
|
||||
pub fn stdout_plain(&self) -> Txt {
|
||||
remove_ansi_csi(&self.stdout)
|
||||
}
|
||||
|
||||
/// Get `stderr` without any ANSI escape sequences (CSI).
|
||||
pub fn stderr_plain(&self) -> Txt {
|
||||
remove_ansi_csi(&self.stderr)
|
||||
}
|
||||
|
||||
/// Gets if `stderr` contains a crash panic.
|
||||
pub fn has_panic(&self) -> bool {
|
||||
if self.code == Some(101) {
|
||||
CrashPanic::contains(&self.stderr_plain())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets if `stderr` contains a crash panic that traced widget/window path.
|
||||
pub fn has_panic_widget(&self) -> bool {
|
||||
if self.code == Some(101) {
|
||||
CrashPanic::contains_widget(&self.stderr_plain())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Try parse `stderr` for the crash panic.
|
||||
///
|
||||
/// Only reliably works if the panic fully printed correctly and was formatted by the panic
|
||||
/// hook installed by `crash_handler` or by the display print of [`CrashPanic`].
|
||||
pub fn find_panic(&self) -> Option<CrashPanic> {
|
||||
if self.code == Some(101) {
|
||||
CrashPanic::find(&self.stderr)
|
||||
CrashPanic::find(&self.stderr_plain())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -254,15 +295,17 @@ impl CrashError {
|
|||
} else if let Some(msg) = self.minidump_message() {
|
||||
msg
|
||||
} else {
|
||||
"Error.".into()
|
||||
"".into()
|
||||
};
|
||||
use std::fmt::Write as _;
|
||||
|
||||
if let Some(c) = self.code {
|
||||
write!(&mut msg, " Code: {c:#x}.").unwrap();
|
||||
let sep = if msg.is_empty() { "" } else { "\n" };
|
||||
write!(&mut msg, "{sep}Code: {c:#X}").unwrap();
|
||||
}
|
||||
if let Some(c) = self.signal {
|
||||
write!(&mut msg, " Signal: {c}.").unwrap();
|
||||
let sep = if msg.is_empty() { "" } else { "\n" };
|
||||
write!(&mut msg, "{sep}Signal: {c}").unwrap();
|
||||
}
|
||||
msg.end_mut();
|
||||
msg
|
||||
|
@ -300,6 +343,29 @@ impl CrashError {
|
|||
}
|
||||
}
|
||||
|
||||
const CSI: &str = "\x1b[";
|
||||
|
||||
/// Remove ANSI escape sequences (CSI) from `s`.
|
||||
pub fn remove_ansi_csi(mut s: &str) -> Txt {
|
||||
fn is_esc_end(byte: u8) -> bool {
|
||||
(0x40..=0x7e).contains(&byte)
|
||||
}
|
||||
|
||||
let mut r = String::new();
|
||||
while let Some(i) = s.find(CSI) {
|
||||
r.push_str(&s[..i]);
|
||||
s = &s[i + CSI.len()..];
|
||||
let mut esc_end = 0;
|
||||
while esc_end < s.len() && !is_esc_end(s.as_bytes()[esc_end]) {
|
||||
esc_end += 1;
|
||||
}
|
||||
esc_end += 1;
|
||||
s = &s[esc_end..];
|
||||
}
|
||||
r.push_str(s);
|
||||
r.into()
|
||||
}
|
||||
|
||||
/// Panic parsed from a `stderr` dump.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub struct CrashPanic {
|
||||
|
@ -332,11 +398,32 @@ impl fmt::Display for CrashPanic {
|
|||
}
|
||||
}
|
||||
impl CrashPanic {
|
||||
/// Gets if `stderr` contains a panic that can be parsed by [`find`].
|
||||
///
|
||||
/// [`find`]: Self::find
|
||||
pub fn contains(stderr: &str) -> bool {
|
||||
Self::find_impl(stderr, false).is_some()
|
||||
}
|
||||
|
||||
/// Gets if `stderr` contains a panic that can be parsed by [`find`] and traced a widget/window path.
|
||||
///
|
||||
/// [`find`]: Self::find
|
||||
pub fn contains_widget(stderr: &str) -> bool {
|
||||
match Self::find_impl(stderr, false) {
|
||||
Some(p) => !p.widget_path.is_empty(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try parse `stderr` for the crash panic.
|
||||
///
|
||||
/// Only reliably works if the panic fully printed correctly and was formatted by the panic
|
||||
/// hook installed by `crash_handler` or by the display print of this type.
|
||||
pub fn find(stderr: &str) -> Option<Self> {
|
||||
Self::find_impl(stderr, true)
|
||||
}
|
||||
|
||||
fn find_impl(stderr: &str, parse: bool) -> Option<Self> {
|
||||
let mut panic_at = usize::MAX;
|
||||
let mut widget_path = usize::MAX;
|
||||
let mut stack_backtrace = usize::MAX;
|
||||
|
@ -358,6 +445,22 @@ impl CrashPanic {
|
|||
return None;
|
||||
}
|
||||
|
||||
if !parse {
|
||||
return Some(Self {
|
||||
thread: Txt::from(""),
|
||||
message: Txt::from(""),
|
||||
file: Txt::from(""),
|
||||
line: 0,
|
||||
column: 0,
|
||||
widget_path: if widget_path < stderr.len() {
|
||||
Txt::from("true")
|
||||
} else {
|
||||
Txt::from("")
|
||||
},
|
||||
backtrace: Txt::from(""),
|
||||
});
|
||||
}
|
||||
|
||||
let panic_str = stderr[panic_at..].lines().next().unwrap();
|
||||
let (thread, location) = panic_str.strip_prefix("thread '").unwrap().split_once("' panicked at ").unwrap();
|
||||
let mut location = location.split(':');
|
||||
|
@ -382,7 +485,7 @@ impl CrashPanic {
|
|||
}
|
||||
|
||||
let widget_path = if widget_path < stderr.len() {
|
||||
stderr[widget_path..].lines().nth(1).unwrap().trim()
|
||||
stderr[widget_path..].lines().next().unwrap().trim()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
@ -482,7 +585,7 @@ fn crash_handler_monitor_process(mut cfg_app: ConfigProcess, mut cfg_dialog: Con
|
|||
}
|
||||
|
||||
tracing::error!(
|
||||
"app-process crashed with exit code ({:#x}), signal ({:#?}), {} crashes previously",
|
||||
"app-process crashed with exit code ({:#X}), signal ({:#?}), {} crashes previously",
|
||||
code.unwrap_or(0),
|
||||
signal.unwrap_or(0),
|
||||
dialog_args.app_crashes.len()
|
||||
|
|
|
@ -411,7 +411,7 @@ impl Controller {
|
|||
if !killed_by_us {
|
||||
let code = code.unwrap_or(0);
|
||||
let signal = signal.unwrap_or(0);
|
||||
tracing::error!(target: "vp_respawn", "view-process exit code: {code:#x}, signal: {signal}");
|
||||
tracing::error!(target: "vp_respawn", "view-process exit code: {code:#X}, signal: {signal}");
|
||||
}
|
||||
|
||||
let stderr = match String::from_utf8(c.stderr) {
|
||||
|
|
|
@ -537,4 +537,256 @@ pub use zng_ext_single_instance::{is_single_instance, single_instance, single_in
|
|||
pub mod crash_handler {
|
||||
#[cfg(feature = "crash_handler")]
|
||||
pub use zng_app::crash_handler::{init, restart_count, CrashArgs, CrashConfig, CrashError, CrashPanic};
|
||||
|
||||
/// Init a crash-handler with dialog that shows detailed debug info.
|
||||
pub fn init_debug() {
|
||||
init(debug_config())
|
||||
}
|
||||
|
||||
/// Config used by [`init_debug`]
|
||||
pub fn debug_config() -> CrashConfig {
|
||||
CrashConfig::new(super::crash_handler_dbg::dialog)
|
||||
}
|
||||
}
|
||||
|
||||
mod crash_handler_dbg {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{
|
||||
ansi_text::AnsiText,
|
||||
app::crash_handler::*,
|
||||
color::ColorScheme,
|
||||
prelude::*,
|
||||
widget::node::{presenter, BoxedUiNode},
|
||||
};
|
||||
|
||||
pub fn dialog(args: CrashArgs) -> ! {
|
||||
if let Some(c) = &args.dialog_crash {
|
||||
eprintln!("DEBUG CRASH DIALOG CRASHED");
|
||||
eprintln!(" {}", c.message());
|
||||
eprintln!("APP CRASH");
|
||||
eprintln!(" {}", args.latest().message());
|
||||
args.exit(0xBADC0DE)
|
||||
}
|
||||
|
||||
// Just in case no view-process was inited before the crash_handler setup.
|
||||
// Multiple view-process inits are ok because they either do nothing or never return.
|
||||
#[cfg(feature = "view_prebuilt")]
|
||||
crate::view_process::prebuilt::init();
|
||||
#[cfg(feature = "view")]
|
||||
crate::view_process::default::init();
|
||||
|
||||
APP.defaults().run_window(async_clmv!(args, {
|
||||
let error = args.latest();
|
||||
Window! {
|
||||
title = APP.about().map(|a| formatx!("{}App Crashed", a.title_prefix()));
|
||||
start_position = zng::window::StartPosition::CenterMonitor;
|
||||
color_scheme = ColorScheme::Dark;
|
||||
|
||||
on_load = hn_once!(|_| {
|
||||
// force to foreground
|
||||
let _ = WINDOWS.focus(WINDOW.id());
|
||||
});
|
||||
on_close = hn_once!(args, |_| {
|
||||
args.exit(0);
|
||||
});
|
||||
|
||||
padding = 5;
|
||||
child_top = header(error), 5;
|
||||
child = panels(error);
|
||||
child_bottom = commands(args), 5;
|
||||
}
|
||||
}));
|
||||
|
||||
args.exit(0);
|
||||
}
|
||||
|
||||
fn header(error: &CrashError) -> impl UiNode {
|
||||
SelectableText! {
|
||||
txt = error.message();
|
||||
layout::margin = 10;
|
||||
}
|
||||
}
|
||||
|
||||
fn panels(error: &CrashError) -> impl UiNode {
|
||||
let mut options = vec![ErrorPanel::Summary];
|
||||
let mut active = ErrorPanel::Summary;
|
||||
|
||||
if !error.stdout.is_empty() {
|
||||
options.push(ErrorPanel::StdoutPlain);
|
||||
active = ErrorPanel::StdoutPlain;
|
||||
if !error.is_stdout_plain() {
|
||||
options.push(ErrorPanel::Stdout);
|
||||
active = ErrorPanel::Stdout;
|
||||
}
|
||||
}
|
||||
|
||||
if !error.stderr.is_empty() {
|
||||
options.push(ErrorPanel::StderrPlain);
|
||||
active = ErrorPanel::StderrPlain;
|
||||
if !error.is_stderr_plain() {
|
||||
options.push(ErrorPanel::Stderr);
|
||||
active = ErrorPanel::Stderr;
|
||||
}
|
||||
}
|
||||
|
||||
if error.has_panic() {
|
||||
options.push(ErrorPanel::Panic);
|
||||
active = ErrorPanel::Panic;
|
||||
}
|
||||
if error.has_panic_widget() {
|
||||
options.push(ErrorPanel::Widget);
|
||||
}
|
||||
if error.minidump.is_some() {
|
||||
options.push(ErrorPanel::Minidump);
|
||||
active = ErrorPanel::Minidump;
|
||||
}
|
||||
|
||||
let active = var(active);
|
||||
|
||||
Container! {
|
||||
child_top = Wrap! {
|
||||
toggle::selector = toggle::Selector::single(active.clone());
|
||||
children = options.iter().map(|p| Toggle! {
|
||||
child = Text!(p.title());
|
||||
value = *p;
|
||||
}).collect::<UiNodeVec>();
|
||||
toggle::style_fn = Style! {
|
||||
layout::padding = (2, 4);
|
||||
widget::corner_radius = 2;
|
||||
};
|
||||
spacing = 5;
|
||||
}, 5;
|
||||
child = presenter(active, wgt_fn!(error, |p: ErrorPanel| p.panel(&error)));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum ErrorPanel {
|
||||
Summary,
|
||||
Stdout,
|
||||
Stderr,
|
||||
StdoutPlain,
|
||||
StderrPlain,
|
||||
Panic,
|
||||
Widget,
|
||||
Minidump,
|
||||
}
|
||||
impl ErrorPanel {
|
||||
fn title(&self) -> Txt {
|
||||
match self {
|
||||
ErrorPanel::Summary => "Summary",
|
||||
ErrorPanel::Stdout => "Stdout",
|
||||
ErrorPanel::Stderr => "Stderr",
|
||||
ErrorPanel::StdoutPlain => "Stdout (plain)",
|
||||
ErrorPanel::StderrPlain => "Stderr (plain)",
|
||||
ErrorPanel::Panic => "Panic",
|
||||
ErrorPanel::Widget => "Widget",
|
||||
ErrorPanel::Minidump => "Minidump",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn panel(&self, error: &CrashError) -> BoxedUiNode {
|
||||
match self {
|
||||
ErrorPanel::Summary => summary_panel(error).boxed(),
|
||||
ErrorPanel::Stdout => std_panel(error.stdout.clone()).boxed(),
|
||||
ErrorPanel::Stderr => std_panel(error.stderr.clone()).boxed(),
|
||||
ErrorPanel::StdoutPlain => std_plain_panel(error.stdout_plain()).boxed(),
|
||||
ErrorPanel::StderrPlain => std_plain_panel(error.stderr_plain()).boxed(),
|
||||
ErrorPanel::Panic => panic_panel(error.find_panic().unwrap()).boxed(),
|
||||
ErrorPanel::Widget => widget_panel(error.find_panic().unwrap().widget_path).boxed(),
|
||||
ErrorPanel::Minidump => minidump_panel(error.minidump.clone().unwrap()).boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn summary_panel(error: &CrashError) -> impl UiNode {
|
||||
let s = formatx!(
|
||||
"Timestamp: {}\nExit Code: {}\nSignal: {}\nStderr: {} bytes\nStdout: {} bytes\nPanic: {}\nMinidump: {}\n\nArgs: {:?}\n",
|
||||
error.unix_time(),
|
||||
match error.code {
|
||||
Some(c) => format!("{c:#x}"),
|
||||
None => "<none>".to_owned(),
|
||||
},
|
||||
match error.signal {
|
||||
Some(c) => format!("{c}"),
|
||||
None => "<none>".to_owned(),
|
||||
},
|
||||
error.stderr.len(),
|
||||
error.stdout.len(),
|
||||
error.find_panic().is_some(),
|
||||
match &error.minidump {
|
||||
Some(p) => {
|
||||
let path = p.display().to_string();
|
||||
let path = path.trim_start_matches(r"\\?\");
|
||||
path.to_owned()
|
||||
}
|
||||
None => "<none>".to_owned(),
|
||||
},
|
||||
error.args,
|
||||
);
|
||||
plain_panel(s)
|
||||
}
|
||||
|
||||
fn std_panel(std: Txt) -> impl UiNode {
|
||||
Scroll! {
|
||||
child_align = Align::TOP_START;
|
||||
widget::background_color = colors::BLACK;
|
||||
layout::padding = 5;
|
||||
child = AnsiText! {
|
||||
txt = std;
|
||||
font_size = 0.9.em();
|
||||
}
|
||||
}
|
||||
}
|
||||
fn std_plain_panel(std: Txt) -> impl UiNode {
|
||||
plain_panel(std)
|
||||
}
|
||||
fn panic_panel(panic: CrashPanic) -> impl UiNode {
|
||||
plain_panel(panic.to_txt())
|
||||
}
|
||||
fn widget_panel(widget_path: Txt) -> impl UiNode {
|
||||
plain_panel(widget_path)
|
||||
}
|
||||
fn minidump_panel(path: PathBuf) -> impl UiNode {
|
||||
let path = path.display().to_string();
|
||||
let path = path.trim_start_matches(r"\\?\");
|
||||
plain_panel(path.to_txt())
|
||||
}
|
||||
fn plain_panel(txt: Txt) -> impl UiNode {
|
||||
Scroll! {
|
||||
child_align = Align::TOP_START;
|
||||
widget::background_color = colors::BLACK;
|
||||
layout::padding = 5;
|
||||
child = SelectableText! {
|
||||
txt;
|
||||
font_size = 0.9.em();
|
||||
// same as AnsiText
|
||||
font_family = ["JetBrains Mono", "Consolas", "monospace"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn commands(args: CrashArgs) -> impl UiNode {
|
||||
Stack! {
|
||||
spacing = 5;
|
||||
direction = StackDirection::start_to_end();
|
||||
layout::align = Align::END;
|
||||
children = ui_vec![
|
||||
Button! {
|
||||
child = Text!("Restart App");
|
||||
on_click = hn_once!(args, |_| {
|
||||
args.restart();
|
||||
});
|
||||
},
|
||||
Button! {
|
||||
child = Text!("Exit App");
|
||||
on_click = hn_once!(|_| {
|
||||
args.exit(0);
|
||||
});
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,7 +198,7 @@ Source: [respawn.rs](./respawn.rs)
|
|||
cargo do run respawn
|
||||
```
|
||||
|
||||
Demonstrates the view-process respawn error recovery feature.
|
||||
Demonstrates app-process crash handler and view-process respawn.
|
||||
|
||||
### `scroll`
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("animation");
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
//let rec = examples_util::record_profile("border");
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("button");
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
//let rec = examples_util::record_profile("calculator");
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("button");
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// view_process::run_same_process(app_main);
|
||||
app_main();
|
||||
|
|
|
@ -15,6 +15,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// view_process::run_same_process(app_main);
|
||||
app_main();
|
||||
|
|
|
@ -11,6 +11,7 @@ use zng_view::extensions::ViewExtensions;
|
|||
|
||||
fn main() {
|
||||
examples_util::print_info();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// zng_view::init_extended(view_extensions);
|
||||
// app_main();
|
||||
|
|
|
@ -25,6 +25,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("focus");
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// view_process::run_same_process(app_main);
|
||||
app_main();
|
||||
|
|
|
@ -27,6 +27,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("icon");
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ use zng_wgt_webrender_debug as wr;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
// view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("image");
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("layer");
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("localize");
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("markdown");
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 56 KiB |
|
@ -15,7 +15,8 @@ fn main() {
|
|||
examples_util::print_info();
|
||||
|
||||
// init crash-handler before view to use different view for the crash dialog app.
|
||||
zng::app::crash_handler::init(zng::app::crash_handler::CrashConfig::new(app_crash_dialog));
|
||||
zng::app::crash_handler::init_debug();
|
||||
// zng::app::crash_handler::init(zng::app::crash_handler::CrashConfig::new(app_crash_dialog));
|
||||
|
||||
// this is the normal app-process:
|
||||
|
||||
|
@ -32,29 +33,49 @@ fn main() {
|
|||
child = Stack! {
|
||||
direction = StackDirection::top_to_bottom();
|
||||
layout::margin = 10;
|
||||
spacing = 5;
|
||||
spacing = 10;
|
||||
children_align = Align::TOP;
|
||||
children = ui_vec![
|
||||
Markdown! {
|
||||
layout::margin = (20, 0, 0, 0);
|
||||
txt = "The renderer and OS windows are created in separate process, the `view-process`. \
|
||||
It automatically respawns in case of a graphics driver crash or other similar fatal error.";
|
||||
},
|
||||
view_respawn(),
|
||||
view_crash(),
|
||||
Wrap! {
|
||||
spacing = 5;
|
||||
children = ui_vec![
|
||||
view_respawn(),
|
||||
view_crash(),
|
||||
],
|
||||
},
|
||||
Markdown! {
|
||||
layout::margin = (20, 0, 0, 0);
|
||||
txt = "When the app is instantiated the crash handler takes over the process, becoming the `monitor-process`. \
|
||||
It spawns the `app-process` that is the normal execution. If the `app-process` crashes it spawns the \
|
||||
`dialog-process` that runs a different app that shows an error message.";
|
||||
},
|
||||
app_crash(true),
|
||||
app_crash(false),
|
||||
Wrap! {
|
||||
spacing = 5;
|
||||
children = ui_vec![
|
||||
app_crash("panic"),
|
||||
app_crash("access violation"),
|
||||
app_crash("stack overflow"),
|
||||
app_crash("custom exit"),
|
||||
],
|
||||
},
|
||||
Markdown! {
|
||||
layout::margin = (20, 0, 0, 0);
|
||||
txt = "The states of these buttons is only preserved for `view-process` crashes. \
|
||||
use `CONFIG` or some other state saving to better recover from `app-process` crashes.";
|
||||
},
|
||||
click_counter(),
|
||||
click_counter(),
|
||||
image(),
|
||||
Wrap! {
|
||||
spacing = 5;
|
||||
children = ui_vec![
|
||||
click_counter(),
|
||||
image(),
|
||||
click_counter(),
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
|
@ -62,33 +83,28 @@ fn main() {
|
|||
}
|
||||
|
||||
// Crash dialog app, runs in the dialog-process.
|
||||
#[allow(unused)]
|
||||
fn app_crash_dialog(args: zng::app::crash_handler::CrashArgs) -> ! {
|
||||
zng::view_process::prebuilt::init();
|
||||
APP.defaults().run_window(async move {
|
||||
Window! {
|
||||
title = "Respawn Example - App Crashed";
|
||||
icon = WindowIcon::render(icon);
|
||||
|
||||
start_position = zng::window::StartPosition::CenterMonitor;
|
||||
auto_size = window::AutoSize::CONTENT;
|
||||
min_size = (300, 100);
|
||||
|
||||
on_load = hn_once!(|_| {
|
||||
// force to foreground
|
||||
let _ = WINDOWS.focus(WINDOW.id());
|
||||
});
|
||||
on_close = hn_once!(args, |_| {
|
||||
args.exit(0);
|
||||
});
|
||||
|
||||
padding = 5;
|
||||
child_top = Markdown!(
|
||||
"The Respawn Example app has crashed.\n\n{}\n\n**Details:**\n",
|
||||
child = Markdown!(
|
||||
"The Respawn Example app has crashed.\n\n{}\n\n",
|
||||
args.latest().message(),
|
||||
), 5;
|
||||
child = Scroll! {
|
||||
padding = 5;
|
||||
child_align = Align::TOP_START;
|
||||
child = zng::ansi_text::AnsiText!(args.latest().to_txt());
|
||||
widget::background_color = colors::BLACK;
|
||||
};
|
||||
);
|
||||
child_bottom = Stack! {
|
||||
spacing = 5;
|
||||
direction = StackDirection::start_to_end();
|
||||
|
@ -117,7 +133,7 @@ fn app_crash_dialog(args: zng::app::crash_handler::CrashArgs) -> ! {
|
|||
}, 5;
|
||||
}
|
||||
});
|
||||
panic!("dialog app did not respond correctly")
|
||||
std::process::exit(0)
|
||||
}
|
||||
|
||||
fn view_respawn() -> impl UiNode {
|
||||
|
@ -143,18 +159,32 @@ fn view_crash() -> impl UiNode {
|
|||
}
|
||||
}
|
||||
|
||||
fn app_crash(panic: bool) -> impl UiNode {
|
||||
fn app_crash(crash_name: &'static str) -> impl UiNode {
|
||||
Button! {
|
||||
child = Text!("Crash App-Process ({})", if panic { "panic" } else { "access violation" });
|
||||
child = Text!("Crash ({crash_name})");
|
||||
on_click = hn!(|_| {
|
||||
if panic {
|
||||
panic!("Test app-process crash!");
|
||||
} else {
|
||||
// SAFETY: deliberate access violation
|
||||
#[allow(deref_nullptr)]
|
||||
unsafe {
|
||||
*std::ptr::null_mut() = true;
|
||||
match crash_name {
|
||||
"panic" => panic!("Test app-process crash!"),
|
||||
"access violation" => {
|
||||
// SAFETY: deliberate access violation
|
||||
#[allow(deref_nullptr)]
|
||||
unsafe {
|
||||
*std::ptr::null_mut() = true;
|
||||
}
|
||||
}
|
||||
"stack overflow" => {
|
||||
fn overflow(c: u64) {
|
||||
if c < u64::MAX {
|
||||
overflow(c + 1)
|
||||
}
|
||||
}
|
||||
overflow(0)
|
||||
}
|
||||
"custom exit" => {
|
||||
eprintln!("custom error");
|
||||
std::process::exit(0xBAD);
|
||||
}
|
||||
n => panic!("Unknown crash '{n}'"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -175,13 +205,9 @@ fn click_counter() -> impl UiNode {
|
|||
}
|
||||
|
||||
fn image() -> impl UiNode {
|
||||
Stack! {
|
||||
direction = StackDirection::top_to_bottom();
|
||||
spacing = 3;
|
||||
children = ui_vec![
|
||||
text::Strong!("Image:"),
|
||||
Image! { source = include_bytes!("res/window/icon-bytes.png"); size = (32, 32); },
|
||||
];
|
||||
Image! {
|
||||
source = include_bytes!("res/window/icon-bytes.png"); size = (32, 32);
|
||||
tooltip = Tip!(Text!("Image reloads after respawn"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ use rand::SeedableRng;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("scroll");
|
||||
// view_process::run_same_process(app_main);
|
||||
|
|
|
@ -9,6 +9,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("shortcuts");
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
//let rec = examples_util::record_profile("text");
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ use zng::view_process::prebuilt as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("transform");
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ use zng::view_process::default as view_process;
|
|||
fn main() {
|
||||
examples_util::print_info();
|
||||
// view_process::init();
|
||||
zng::app::crash_handler::init_debug();
|
||||
|
||||
// let rec = examples_util::record_profile("window");
|
||||
|
||||
|
|
Loading…
Reference in New Issue