Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Allow setting the panic hook with parity-clib #9292

Merged
merged 4 commits into from
Aug 10, 2018
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions parity-clib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ name = "parity"
crate-type = ["cdylib", "staticlib"]

[dependencies]
panic_hook = { path = "../util/panic_hook" }
parity-ethereum = { path = "../", default-features = false }

[features]
Expand Down
17 changes: 17 additions & 0 deletions parity-clib/parity.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ void parity_destroy(void* parity);
///
int parity_rpc(void* parity, const char* rpc, size_t len, char* out_str, size_t* out_len);

/// Sets a callback to call when a panic happens in the Rust code.
///
/// The callback takes as parameter the custom param (the one passed to this function), plus the
/// panic message. You are expected to log the panic message somehow, in order to communicate it to
/// the user. A panic always indicates a bug in Parity.
///
/// Note that this method sets the panic hook for the whole program, and not just for Parity. In
/// other words, if you use multiple Rust libraries at once (and not just Parity), then a panic
/// in any Rust code will call this callback as well.
///
/// ## Thread safety
///
/// The callback can be called from any thread and multiple times simultaneously. Make sure that
/// your code is thread safe.
///
int parity_set_panic_hook(void (*cb)(void* param, const char* msg, size_t msg_len), void* param);

#ifdef __cplusplus
}
#endif
Expand Down
223 changes: 112 additions & 111 deletions parity-clib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! duplicating documentation.

extern crate parity_ethereum;
extern crate panic_hook;

use std::os::raw::{c_char, c_void, c_int};
use std::panic;
Expand All @@ -33,132 +34,132 @@ pub struct ParityParams {
}

#[no_mangle]
pub extern fn parity_config_from_cli(args: *const *const c_char, args_lens: *const usize, len: usize, output: *mut *mut c_void) -> c_int {
unsafe {
panic::catch_unwind(|| {
*output = ptr::null_mut();

let args = {
let arg_ptrs = slice::from_raw_parts(args, len);
let arg_lens = slice::from_raw_parts(args_lens, len);

let mut args = Vec::with_capacity(len + 1);
args.push("parity".to_owned());

for (&arg, &len) in arg_ptrs.iter().zip(arg_lens.iter()) {
let string = slice::from_raw_parts(arg as *const u8, len);
match String::from_utf8(string.to_owned()) {
Ok(a) => args.push(a),
Err(_) => return 1,
};
}

args
};

match parity_ethereum::Configuration::parse_cli(&args) {
Ok(mut cfg) => {
// Always disable the auto-updater when used as a library.
cfg.args.arg_auto_update = "none".to_owned();

let cfg = Box::into_raw(Box::new(cfg));
*output = cfg as *mut _;
0
},
Err(_) => {
1
},
pub unsafe extern fn parity_config_from_cli(args: *const *const c_char, args_lens: *const usize, len: usize, output: *mut *mut c_void) -> c_int {
panic::catch_unwind(|| {
*output = ptr::null_mut();

let args = {
let arg_ptrs = slice::from_raw_parts(args, len);
let arg_lens = slice::from_raw_parts(args_lens, len);

let mut args = Vec::with_capacity(len + 1);
args.push("parity".to_owned());

for (&arg, &len) in arg_ptrs.iter().zip(arg_lens.iter()) {
let string = slice::from_raw_parts(arg as *const u8, len);
match String::from_utf8(string.to_owned()) {
Ok(a) => args.push(a),
Err(_) => return 1,
};
}
}).unwrap_or(1)
}

args
};

match parity_ethereum::Configuration::parse_cli(&args) {
Ok(mut cfg) => {
// Always disable the auto-updater when used as a library.
cfg.args.arg_auto_update = "none".to_owned();

let cfg = Box::into_raw(Box::new(cfg));
*output = cfg as *mut _;
0
},
Err(_) => {
1
},
}
}).unwrap_or(1)
}

#[no_mangle]
pub extern fn parity_config_destroy(cfg: *mut c_void) {
unsafe {
let _ = panic::catch_unwind(|| {
let _cfg = Box::from_raw(cfg as *mut parity_ethereum::Configuration);
});
}
pub unsafe extern fn parity_config_destroy(cfg: *mut c_void) {
let _ = panic::catch_unwind(|| {
let _cfg = Box::from_raw(cfg as *mut parity_ethereum::Configuration);
});
}

#[no_mangle]
pub extern fn parity_start(cfg: *const ParityParams, output: *mut *mut c_void) -> c_int {
unsafe {
panic::catch_unwind(|| {
*output = ptr::null_mut();
let cfg: &ParityParams = &*cfg;

let config = Box::from_raw(cfg.configuration as *mut parity_ethereum::Configuration);

let on_client_restart_cb = {
struct Cb(Option<extern "C" fn(*mut c_void, *const c_char, usize)>, *mut c_void);
unsafe impl Send for Cb {}
unsafe impl Sync for Cb {}
impl Cb {
fn call(&self, new_chain: String) {
if let Some(ref cb) = self.0 {
cb(self.1, new_chain.as_bytes().as_ptr() as *const _, new_chain.len())
}
}
}
let cb = Cb(cfg.on_client_restart_cb, cfg.on_client_restart_cb_custom);
move |new_chain: String| { cb.call(new_chain); }
};

let action = match parity_ethereum::start(*config, on_client_restart_cb, || {}) {
Ok(action) => action,
Err(_) => return 1,
};

match action {
parity_ethereum::ExecutionAction::Instant(Some(s)) => { println!("{}", s); 0 },
parity_ethereum::ExecutionAction::Instant(None) => 0,
parity_ethereum::ExecutionAction::Running(client) => {
*output = Box::into_raw(Box::<parity_ethereum::RunningClient>::new(client)) as *mut c_void;
0
}
pub unsafe extern fn parity_start(cfg: *const ParityParams, output: *mut *mut c_void) -> c_int {
panic::catch_unwind(|| {
*output = ptr::null_mut();
let cfg: &ParityParams = &*cfg;

let config = Box::from_raw(cfg.configuration as *mut parity_ethereum::Configuration);

let on_client_restart_cb = {
let cb = CallbackStr(cfg.on_client_restart_cb, cfg.on_client_restart_cb_custom);
move |new_chain: String| { cb.call(&new_chain); }
};

let action = match parity_ethereum::start(*config, on_client_restart_cb, || {}) {
Ok(action) => action,
Err(_) => return 1,
};

match action {
parity_ethereum::ExecutionAction::Instant(Some(s)) => { println!("{}", s); 0 },
parity_ethereum::ExecutionAction::Instant(None) => 0,
parity_ethereum::ExecutionAction::Running(client) => {
*output = Box::into_raw(Box::<parity_ethereum::RunningClient>::new(client)) as *mut c_void;
0
}
}).unwrap_or(1)
}
}
}).unwrap_or(1)
}

#[no_mangle]
pub extern fn parity_destroy(client: *mut c_void) {
unsafe {
let _ = panic::catch_unwind(|| {
let client = Box::from_raw(client as *mut parity_ethereum::RunningClient);
client.shutdown();
});
}
pub unsafe extern fn parity_destroy(client: *mut c_void) {
let _ = panic::catch_unwind(|| {
let client = Box::from_raw(client as *mut parity_ethereum::RunningClient);
client.shutdown();
});
}

#[no_mangle]
pub extern fn parity_rpc(client: *mut c_void, query: *const char, len: usize, out_str: *mut c_char, out_len: *mut usize) -> c_int {
unsafe {
panic::catch_unwind(|| {
let client: &mut parity_ethereum::RunningClient = &mut *(client as *mut parity_ethereum::RunningClient);

let query_str = {
let string = slice::from_raw_parts(query as *const u8, len);
match str::from_utf8(string) {
Ok(a) => a,
Err(_) => return 1,
}
};

if let Some(output) = client.rpc_query_sync(query_str) {
let q_out_len = output.as_bytes().len();
if *out_len < q_out_len {
return 1;
}
pub unsafe extern fn parity_rpc(client: *mut c_void, query: *const char, len: usize, out_str: *mut c_char, out_len: *mut usize) -> c_int {
panic::catch_unwind(|| {
let client: &mut parity_ethereum::RunningClient = &mut *(client as *mut parity_ethereum::RunningClient);

let query_str = {
let string = slice::from_raw_parts(query as *const u8, len);
match str::from_utf8(string) {
Ok(a) => a,
Err(_) => return 1,
}
};

ptr::copy_nonoverlapping(output.as_bytes().as_ptr(), out_str as *mut u8, q_out_len);
*out_len = q_out_len;
0
} else {
1
if let Some(output) = client.rpc_query_sync(query_str) {
let q_out_len = output.as_bytes().len();
if *out_len < q_out_len {
return 1;
}
}).unwrap_or(1)

ptr::copy_nonoverlapping(output.as_bytes().as_ptr(), out_str as *mut u8, q_out_len);
*out_len = q_out_len;
0
} else {
1
}
}).unwrap_or(1)
}

#[no_mangle]
pub unsafe extern fn parity_set_panic_hook(callback: extern "C" fn(*mut c_void, *const c_char, usize), param: *mut c_void) {
let cb = CallbackStr(Some(callback), param);
panic_hook::set_with(move |panic_msg| {
cb.call(panic_msg);
});
}

// Internal structure for handling callbacks that get passed a string.
struct CallbackStr(Option<extern "C" fn(*mut c_void, *const c_char, usize)>, *mut c_void);
unsafe impl Send for CallbackStr {}
unsafe impl Sync for CallbackStr {}
impl CallbackStr {
fn call(&self, new_chain: &str) {
if let Some(ref cb) = self.0 {
cb(self.1, new_chain.as_bytes().as_ptr() as *const _, new_chain.len())
}
}
}
1 change: 0 additions & 1 deletion parity/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ extern crate ethcore_transaction as transaction;
extern crate ethereum_types;
extern crate ethkey;
extern crate kvdb;
extern crate panic_hook;
extern crate parity_hash_fetch as hash_fetch;
extern crate parity_ipfs_api;
extern crate parity_local_store as local_store;
Expand Down
3 changes: 2 additions & 1 deletion parity/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ fn main_direct(force_can_restart: bool) -> i32 {
panic_hook::set_with({
let e = exit.clone();
let exiting = exiting.clone();
move || {
move |panic_msg| {
let _ = stdio::stderr().write_all(panic_msg.as_bytes());
if !exiting.swap(true, Ordering::SeqCst) {
*e.0.lock() = ExitStatus {
panicking: true,
Expand Down
43 changes: 24 additions & 19 deletions util/panic_hook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,26 @@ use std::thread;
use std::process;
use backtrace::Backtrace;

/// Set the panic hook
/// Set the panic hook to write to stderr and abort the process when a panic happens.
pub fn set_abort() {
set_with(|| process::abort());
set_with(|msg| {
let _ = io::stderr().write_all(msg.as_bytes());
process::abort()
});
}

/// Set the panic hook with a closure to be called afterwards.
pub fn set_with<F: Fn() + Send + Sync + 'static>(f: F) {
/// Set the panic hook with a closure to be called. The closure receives the panic message.
///
/// Depending on how Parity was compiled, after the closure has been executed, either the process
/// aborts or unwinding starts.
///
/// If you panic within the closure, a double panic happens and the process will stop.
pub fn set_with<F>(f: F)
where F: Fn(&str) + Send + Sync + 'static
{
panic::set_hook(Box::new(move |info| {
panic_hook(info);
f();
let msg = gen_panic_msg(info);
f(&msg);
}));
}

Expand All @@ -43,7 +53,7 @@ This is a bug. Please report it at:
https://github.com/paritytech/parity-ethereum/issues/new
";

fn panic_hook(info: &PanicInfo) {
fn gen_panic_msg(info: &PanicInfo) -> String {
let location = info.location();
let file = location.as_ref().map(|l| l.file()).unwrap_or("<unknown>");
let line = location.as_ref().map(|l| l.line()).unwrap_or(0);
Expand All @@ -61,18 +71,13 @@ fn panic_hook(info: &PanicInfo) {

let backtrace = Backtrace::new();

let mut stderr = io::stderr();
format!(r#"

let _ = writeln!(stderr, "");
let _ = writeln!(stderr, "====================");
let _ = writeln!(stderr, "");
let _ = writeln!(stderr, "{:?}", backtrace);
let _ = writeln!(stderr, "");
let _ = writeln!(
stderr,
"Thread '{}' panicked at '{}', {}:{}",
name, msg, file, line
);
====================

let _ = writeln!(stderr, "{}", ABOUT_PANIC);
{backtrace:?}

Thread '{name}' panicked at '{msg}', {file}:{line}
{about}
"#, backtrace = backtrace, name = name, msg = msg, file = file, line = line, about = ABOUT_PANIC)
}