diff --git a/util/src/lib.rs b/util/src/lib.rs index 16a25f538..b48352582 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -21,6 +21,7 @@ #![feature(plugin)] #![plugin(clippy)] #![allow(needless_range_loop, match_bool)] +#![feature(std_panic, recover)] //! Ethcore-util library //! //! ### Rust version: @@ -54,7 +55,7 @@ //! cd parity //! cargo build --release //! ``` -//! +//! //! - OSX: //! //! ```bash @@ -129,6 +130,7 @@ pub mod semantic_version; pub mod io; pub mod network; pub mod log; +pub mod panics; pub use common::*; pub use misc::*; diff --git a/util/src/panics.rs b/util/src/panics.rs new file mode 100644 index 000000000..4e1365636 --- /dev/null +++ b/util/src/panics.rs @@ -0,0 +1,148 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Panic utilities + +use std::thread; +use std::panic; +use std::sync::Mutex; +use std::any::Any; +use std::ops::DerefMut; + +pub trait OnPanicListener: Send + Sync + 'static { + fn call(&mut self, arg: &T); +} + +impl OnPanicListener for F + where F: FnMut(&T) + Send + Sync + 'static { + fn call(&mut self, arg: &T) { + self(arg) + } +} + +pub trait ArgsConverter { + fn convert(&self, t: &Box) -> Option; +} + +pub trait MayPanic { + fn on_panic(&mut self, closure: F) + where F: OnPanicListener; +} + +pub trait PanicHandler> : MayPanic{ + fn new(converter: C) -> Self; + fn catch_panic(&mut self, g: G) -> thread::Result + where G: FnOnce() -> R + panic::RecoverSafe; +} + + +pub struct StringConverter; +impl ArgsConverter for StringConverter { + fn convert(&self, t: &Box) -> Option { + t.downcast_ref::<&'static str>().map(|t| t.clone().to_owned()) + } +} + +pub struct BasePanicHandler + where C: ArgsConverter, T: 'static { + converter: C, + listeners: Mutex>>> +} + +impl BasePanicHandler + where C: ArgsConverter, T: 'static { + fn notify_all(&mut self, res: Option) { + if let None = res { + return; + } + let r = res.unwrap(); + let mut listeners = self.listeners.lock().unwrap(); + for listener in listeners.deref_mut() { + listener.call(&r); + } + } +} + +impl PanicHandler for BasePanicHandler + where C: ArgsConverter, T: 'static { + + fn new(converter: C) -> Self { + BasePanicHandler { + converter: converter, + listeners: Mutex::new(vec![]) + } + } + + fn catch_panic(&mut self, g: G) -> thread::Result + where G: FnOnce() -> R + panic::RecoverSafe { + let result = panic::recover(g); + + println!("After calling function"); + if let Err(ref e) = result { + let res = self.converter.convert(e); + println!("Got error. Notifying"); + self.notify_all(res); + } + + result + } +} + +impl MayPanic for BasePanicHandler + where C: ArgsConverter, T: 'static { + fn on_panic(&mut self, closure: F) + where F: OnPanicListener { + self.listeners.lock().unwrap().push(Box::new(closure)); + } +} + +#[test] +fn should_notify_listeners_about_panic () { + use std::sync::{Arc, RwLock}; + + // given + let invocations = Arc::new(RwLock::new(vec![])); + let i = invocations.clone(); + let mut p = BasePanicHandler::new(StringConverter); + p.on_panic(move |t: &String| i.write().unwrap().push(t.clone())); + + // when + p.catch_panic(|| panic!("Panic!")); + + // then + assert!(invocations.read().unwrap()[0] == "Panic!"); +} + +#[test] +fn should_notify_listeners_about_panic_in_other_thread () { + use std::thread; + use std::sync::{Arc, RwLock}; + + // given + let invocations = Arc::new(RwLock::new(vec![])); + let i = invocations.clone(); + let mut p = BasePanicHandler::new(StringConverter); + p.on_panic(move |t: &String| i.write().unwrap().push(t.clone())); + + // when + let t = thread::spawn(move || + p.catch_panic(|| panic!("Panic!")) + ); + t.join(); + + // then + assert!(invocations.read().unwrap()[0] == "Panic!"); +}