diff options
-rw-r--r-- | components/servo/main.rs | 2 | ||||
-rw-r--r-- | components/util/lib.rs | 2 | ||||
-rw-r--r-- | components/util/panicking.rs | 72 | ||||
-rw-r--r-- | components/util/thread.rs | 89 | ||||
-rw-r--r-- | ports/gonk/src/main.rs | 3 |
5 files changed, 115 insertions, 53 deletions
diff --git a/components/servo/main.rs b/components/servo/main.rs index 08ed645b246..b5e2812a595 100644 --- a/components/servo/main.rs +++ b/components/servo/main.rs @@ -35,6 +35,7 @@ extern crate servo; use servo::Browser; use servo::compositing::windowing::WindowEvent; use servo::util::opts::{self, ArgumentParsingResult}; +use servo::util::panicking::initiate_panic_hook; use std::rc::Rc; fn main() { @@ -51,6 +52,7 @@ fn main() { None }; + initiate_panic_hook(); env_logger::init().unwrap(); setup_logging(); diff --git a/components/util/lib.rs b/components/util/lib.rs index 4f69d82ed87..5eb530f1b19 100644 --- a/components/util/lib.rs +++ b/components/util/lib.rs @@ -6,6 +6,7 @@ #![feature(core_intrinsics)] #![feature(custom_derive)] #![cfg_attr(feature = "non-geckolib", feature(decode_utf16))] +#![feature(fnbox)] #![feature(optin_builtin_traits)] #![feature(plugin)] #![feature(panic_handler)] @@ -53,6 +54,7 @@ pub mod linked_list; pub mod non_geckolib; #[allow(unsafe_code)] pub mod opts; +pub mod panicking; #[allow(unsafe_code)] pub mod prefs; pub mod print_tree; diff --git a/components/util/panicking.rs b/components/util/panicking.rs new file mode 100644 index 00000000000..eecfd74b97f --- /dev/null +++ b/components/util/panicking.rs @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use opts; +use std::any::Any; +use std::boxed::FnBox; +use std::cell::RefCell; +use std::io::{Write, stderr}; +use std::panic::{PanicInfo, take_hook, set_hook}; +use std::sync::{Once, ONCE_INIT}; +use std::thread; + +// only set the panic hook once +static HOOK_SET: Once = ONCE_INIT; + +/// TLS data pertaining to how failures should be reported +pub struct PanicHandlerLocal { + /// failure handler passed through spawn_named_with_send_on_failure + pub fail: Box<(FnBox(&(Any + Send))) + Send + 'static> +} + +thread_local!(pub static LOCAL_INFO: RefCell<Option<PanicHandlerLocal>> = RefCell::new(None)); + +/// Initiates the custom panic hook +/// Should be called in main() after arguments have been parsed +pub fn initiate_panic_hook() { + + // store it locally, we can't trust that opts::get() will work whilst panicking + let full_backtraces = opts::get().full_backtraces; + + // Set the panic handler only once. It is global. + HOOK_SET.call_once(|| { + // The original backtrace-printing hook. We still want to call this + let hook = take_hook(); + + let new_hook = move |info: &PanicInfo| { + let payload = info.payload(); + let name = thread::current().name().unwrap_or("<unknown thread>").to_string(); + // Notify error handlers stored in LOCAL_INFO if any + LOCAL_INFO.with(|i| { + if let Some(info) = i.borrow_mut().take() { + debug!("Thread `{}` failed, notifying error handlers", name); + (info.fail).call_box((payload, )); + } + }); + + // Skip cascading SendError/RecvError backtraces if allowed + if !full_backtraces { + if let Some(s) = payload.downcast_ref::<String>() { + if s.contains("SendError") { + let err = stderr(); + let _ = write!(err.lock(), "Thread \"{}\" panicked with an unwrap of \ + `SendError` (backtrace skipped)\n", name); + return; + } else if s.contains("RecvError") { + let err = stderr(); + let _ = write!(err.lock(), "Thread \"{}\" panicked with an unwrap of \ + `RecvError` (backtrace skipped)\n", name); + return; + } + } + } + + // Call the old hook to get the backtrace + hook(&info); + }; + set_hook(Box::new(new_hook)); + }); + + +} diff --git a/components/util/thread.rs b/components/util/thread.rs index aa0baac20aa..85a7c52adf8 100644 --- a/components/util/thread.rs +++ b/components/util/thread.rs @@ -3,15 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use ipc_channel::ipc::IpcSender; -use opts; +use panicking; use serde::Serialize; use std::any::Any; use std::borrow::ToOwned; -use std::io::{Write, stderr}; -use std::panic::{PanicInfo, take_hook, set_hook}; use std::sync::mpsc::Sender; use std::thread; -use std::thread::Builder; use thread_state; pub type PanicReason = Option<String>; @@ -19,40 +16,7 @@ pub type PanicReason = Option<String>; pub fn spawn_named<F>(name: String, f: F) where F: FnOnce() + Send + 'static { - let builder = thread::Builder::new().name(name); - - if opts::get().full_backtraces { - builder.spawn(f).unwrap(); - return; - } - - let f_with_hook = move || { - let hook = take_hook(); - - let new_hook = move |info: &PanicInfo| { - let payload = info.payload(); - if let Some(s) = payload.downcast_ref::<String>() { - if s.contains("SendError") { - let err = stderr(); - let _ = write!(err.lock(), "Thread \"{}\" panicked with an unwrap of \ - `SendError` (backtrace skipped)\n", - thread::current().name().unwrap_or("<unknown thread>")); - return; - } else if s.contains("RecvError") { - let err = stderr(); - let _ = write!(err.lock(), "Thread \"{}\" panicked with an unwrap of \ - `RecvError` (backtrace skipped)\n", - thread::current().name().unwrap_or("<unknown thread>")); - return; - } - } - hook(&info); - }; - set_hook(Box::new(new_hook)); - f(); - }; - - builder.spawn(f_with_hook).unwrap(); + spawn_named_with_send_on_failure_maybe(name, None, f, |_| {}); } pub trait AddFailureDetails { @@ -100,20 +64,39 @@ pub fn spawn_named_with_send_on_failure<F, T, S>(name: String, S: Send + SendOnFailure + 'static, S::Value: From<T>, { - let future_handle = thread::Builder::new().name(name.to_owned()).spawn(move || { - thread_state::initialize(state); - f() - }).unwrap(); - - let watcher_name = format!("{}Watcher", name); - Builder::new().name(watcher_name).spawn(move || { - match future_handle.join() { - Ok(()) => (), - Err(err) => { - debug!("{} failed, notifying constellation", name); - msg.add_panic_object(&*err); - dest.send_on_failure(S::Value::from(msg)); - } + spawn_named_with_send_on_failure_maybe(name, Some(state), f, + move |err| { + msg.add_panic_object(err); + dest.send_on_failure(S::Value::from(msg)); + }); +} + +fn spawn_named_with_send_on_failure_maybe<F, G>(name: String, + state: Option<thread_state::ThreadState>, + f: F, + fail: G) + where F: FnOnce() + Send + 'static, + G: FnOnce(&(Any + Send)) + Send + 'static { + + + let builder = thread::Builder::new().name(name.clone()); + + let local = panicking::PanicHandlerLocal { + fail: Box::new(fail), + }; + + let f_with_state = move || { + if let Some(state) = state { + thread_state::initialize(state); } - }).unwrap(); + + // set the handler + panicking::LOCAL_INFO.with(|i| { + *i.borrow_mut() = Some(local); + }); + f(); + }; + + + builder.spawn(f_with_state).unwrap(); } diff --git a/ports/gonk/src/main.rs b/ports/gonk/src/main.rs index 2d8cb9ca551..6d10a3ac833 100644 --- a/ports/gonk/src/main.rs +++ b/ports/gonk/src/main.rs @@ -47,6 +47,7 @@ use compositing::windowing::WindowEvent; use servo::Browser; use std::env; use util::opts; +use util::panicking; mod input; mod window; @@ -61,6 +62,8 @@ fn main() { // Parse the command line options and store them globally opts::from_cmdline_args(env::args().collect::<Vec<_>>().as_slice()); + panicking::initiate_panic_hook(); + let window = window::Window::new(); // Our wrapper around `Browser` that also implements some |