Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): handle rust panics in wasm module gracefully [wip] #1310

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages_rs/nextclade-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ console_error_panic_hook = "=0.1.7"
eyre = "=0.6.8"
getrandom = { version = "=0.2.10", features = ["js"] }
itertools = "=0.11.0"
js-sys = { version = "=0.3.64", features = [] }
js-sys = "=0.3.64"
log = "=0.4.19"
nextclade = { path = "../nextclade" }
schemars = { version = "=0.8.12", features = ["chrono", "either", "enumset", "indexmap1"] }
serde = { version = "=1.0.164", features = ["derive"] }
serde-wasm-bindgen = { version = "=0.5.0" }
wasm-bindgen = { version = "=0.2.87", features = ["serde-serialize"] }
wasm-logger = "=0.2.0"
web-sys = { version = "=0.3.64", features = ["console"] }
web-sys = { version = "=0.3.64", features = ["console", "Window", "Event", "CustomEvent", "Worker", "WorkerGlobalScope", "WorkerNavigator", "WorkerOptions", "WorkerLocation"] }

[build-dependencies]
nextclade = { path = "../nextclade" }
Expand Down
3 changes: 2 additions & 1 deletion packages_rs/nextclade-web/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::wasm::panic_hook;
use wasm_bindgen::prelude::wasm_bindgen;

#[wasm_bindgen(start)]
pub fn main() {
wasm_logger::init(wasm_logger::Config::default());
console_error_panic_hook::set_once();
panic_hook::set_once();
}

mod wasm;
1 change: 1 addition & 0 deletions packages_rs/nextclade-web/src/wasm/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod jserr;
pub mod main;
pub mod panic_hook;
pub mod seq_autodetect;
119 changes: 119 additions & 0 deletions packages_rs/nextclade-web/src/wasm/panic_hook.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use eyre::Report;
/// Adapted from https://github.com/rustwasm/console_error_panic_hook
use log;
use schemars::_private::NoSerialize;
use std::panic::{set_hook, PanicInfo};
use std::sync::Once;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsError, JsValue};
use web_sys::Event;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn error(msg: String);

type Error;

#[wasm_bindgen(constructor)]
fn new() -> Error;

#[wasm_bindgen(structural, method, getter)]
fn stack(error: &Error) -> String;
}

fn hook_impl(info: &PanicInfo) {
let mut msg = info.to_string();

// Add the error stack to our message.
//
// This ensures that even if the `console` implementation doesn't
// include stacks for `console.error`, the stack is still available
// for the user. Additionally, Firefox's console tries to clean up
// stack traces, and ruins Rust symbols in the process
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1519569) but since
// it only touches the logged message's associated stack, and not
// the message's contents, by including the stack in the message
// contents we make sure it is available to the user.
msg.push_str("\n\nStack:\n\n");
let e = Error::new();
let stack = e.stack();
msg.push_str(&stack);

// Safari's devtools, on the other hand, _do_ mess with logged
// messages' contents, so we attempt to break their heuristics for
// doing that by appending some whitespace.
// https://github.com/rustwasm/console_error_panic_hook/issues/7
msg.push_str("\n\n");

js_sys::eval(
// language=javascript
r#"
'use strict';

(function() {
throw new Error('<<<<<<<<<<<<<<< HELLOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO >>>>>>>>>>>>>>>')
})()
"#,
)
.map_err(|err| {
log::error!("js_sys::eval failed: {err:#?}");
})
.ok();

send_error_event().unwrap();

// Finally, log the panic with `console.error`!
// log::error!("{}", &msg);
// error(msg);

// wasm_bindgen::throw_val(JsValue::from_str("Hello"));

// wasm_bindgen::throw_val(JsValue::from(JsError::new(&msg)));

// js_sys::Function::new_no_args("{\
//
// \
// ");
//
// JsError::new("message")
}

fn send_error_event() -> Result<(), JsValue> {
let global = js_sys::global();
if global.is_null() {
log::info!("{}", global.to_string());
} else {
log::info!("no global");
}

if let Some(window) = web_sys::window() {
log::info!("window");
if !window.dispatch_event(&Event::new("error")?)? {
log::error!("Unable to dispatch error event");
}
} else {
log::info!("no window");
}
Ok(())
}

/// A panic hook for use with
/// [`std::panic::set_hook`](https://doc.rust-lang.org/nightly/std/panic/fn.set_hook.html)
/// that logs panics into
/// [`console.error`](https://developer.mozilla.org/en-US/docs/Web/API/Console/error).
///
/// On non-wasm targets, prints the panic to `stderr`.
pub fn hook(info: &PanicInfo) {
hook_impl(info);
}

/// Set the `console.error` panic hook the first time this is called. Subsequent
/// invocations do nothing.
#[inline]
pub fn set_once() {
static SET_HOOK: Once = Once::new();
SET_HOOK.call_once(|| {
set_hook(Box::new(hook));
});
}
6 changes: 6 additions & 0 deletions packages_rs/nextclade-web/src/workers/launcher.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class LauncherWorkerImpl {
}

private onError(error: unknown) {
console.log('onError', { this: this, error })

if (!this) {
return
}

this.analysisGlobalStatusObservable.next(AlgorithmGlobalStatus.failed)
this.analysisResultsObservable.error(sanitizeError(error))
void this.destroy() // eslint-disable-line no-void
Expand Down
Loading