use crate::*;
use extendr_ffi::{
R_MakeUnwindCont, R_UnwindProtect, Rboolean, Rf_error, Rf_protect, Rf_unprotect,
};
use std::cell::Cell;
use std::sync::Mutex;
static R_API_LOCK: Mutex<()> = Mutex::new(());
thread_local! {
static THREAD_HAS_LOCK: Cell<bool> = const { Cell::new(false) };
}
pub fn single_threaded<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
let has_lock = THREAD_HAS_LOCK.with(|x| x.get());
let _guard = if !has_lock {
Some(R_API_LOCK.lock().unwrap())
} else {
None
};
THREAD_HAS_LOCK.with(|x| x.set(true));
let result = f();
if _guard.is_some() {
THREAD_HAS_LOCK.with(|x| x.set(false));
}
result
}
static mut R_ERROR_BUF: Option<std::ffi::CString> = None;
pub fn throw_r_error<S: AsRef<str>>(s: S) -> ! {
let s = s.as_ref();
unsafe {
R_ERROR_BUF = Some(std::ffi::CString::new(s).unwrap());
let ptr = std::ptr::addr_of!(R_ERROR_BUF);
Rf_error((*ptr).as_ref().unwrap().as_ptr());
};
}
pub fn catch_r_error<F>(f: F) -> Result<SEXP>
where
F: FnOnce() -> SEXP + Copy,
F: std::panic::UnwindSafe,
{
use std::os::raw;
unsafe extern "C" fn do_call<F>(data: *mut raw::c_void) -> SEXP
where
F: FnOnce() -> SEXP + Copy,
{
let data = data as *const ();
let f: &F = &*(data as *const F);
f()
}
unsafe extern "C" fn do_cleanup(_: *mut raw::c_void, jump: Rboolean) {
if jump != Rboolean::FALSE {
panic!("R has thrown an error.");
}
}
single_threaded(|| unsafe {
let fun_ptr = do_call::<F> as *const ();
let clean_ptr = do_cleanup as *const ();
let x = false;
let fun = std::mem::transmute::<
*const (),
Option<unsafe extern "C" fn(*mut std::ffi::c_void) -> *mut extendr_ffi::SEXPREC>,
>(fun_ptr);
let cleanfun = std::mem::transmute::<
*const (),
std::option::Option<unsafe extern "C" fn(*mut std::ffi::c_void, extendr_ffi::Rboolean)>,
>(clean_ptr);
let data = &f as *const _ as _;
let cleandata = &x as *const _ as _;
let cont = R_MakeUnwindCont();
Rf_protect(cont);
let res = match std::panic::catch_unwind(|| {
R_UnwindProtect(fun, data, cleanfun, cleandata, cont)
}) {
Ok(res) => Ok(res),
Err(_) => Err("Error in protected R code".into()),
};
Rf_unprotect(1);
res
})
}
#[no_mangle]
pub extern "C" fn register_extendr_panic_hook() {
static RUN_ONCE: std::sync::Once = std::sync::Once::new();
RUN_ONCE.call_once_force(|x| {
if x.is_poisoned() {
println!("warning: extendr panic hook info registration was done more than once");
return;
}
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |x| {
let show_traceback = std::env::var("EXTENDR_BACKTRACE")
.map(|v| v.eq_ignore_ascii_case("true") || v == "1")
.unwrap_or(false);
if show_traceback {
default_hook(x)
}
}));
});
}