extendr_api/
thread_safety.rs1use crate::*;
3use extendr_ffi::{
4 R_MakeUnwindCont, R_UnwindProtect, Rboolean, Rf_error, Rf_protect, Rf_unprotect,
5};
6use std::cell::Cell;
7use std::sync::Mutex;
8
9static R_API_LOCK: Mutex<()> = Mutex::new(());
12
13thread_local! {
14 static THREAD_HAS_LOCK: Cell<bool> = const { Cell::new(false) };
15}
16
17pub fn single_threaded<F, R>(f: F) -> R
24where
25 F: FnOnce() -> R,
26{
27 let has_lock = THREAD_HAS_LOCK.with(|x| x.get());
28
29 let _guard = if !has_lock {
31 Some(R_API_LOCK.lock().unwrap())
32 } else {
33 None
34 };
35
36 THREAD_HAS_LOCK.with(|x| x.set(true));
38
39 let result = f();
40
41 if _guard.is_some() {
43 THREAD_HAS_LOCK.with(|x| x.set(false));
44 }
45
46 result
47}
48
49static mut R_ERROR_BUF: Option<std::ffi::CString> = None;
50
51pub fn throw_r_error<S: AsRef<str>>(s: S) -> ! {
52 let s = s.as_ref();
53 unsafe {
54 R_ERROR_BUF = Some(std::ffi::CString::new(s).unwrap());
55 let ptr = std::ptr::addr_of!(R_ERROR_BUF);
56 Rf_error((*ptr).as_ref().unwrap().as_ptr());
57 };
58}
59
60pub fn catch_r_error<F>(f: F) -> Result<SEXP>
72where
73 F: FnOnce() -> SEXP + Copy,
74 F: std::panic::UnwindSafe,
75{
76 use std::os::raw;
77
78 unsafe extern "C" fn do_call<F>(data: *mut raw::c_void) -> SEXP
79 where
80 F: FnOnce() -> SEXP + Copy,
81 {
82 let data = data as *const ();
83 let f: &F = &*(data as *const F);
84 f()
85 }
86
87 unsafe extern "C" fn do_cleanup(_: *mut raw::c_void, jump: Rboolean) {
88 if jump != Rboolean::FALSE {
89 panic!("R has thrown an error.");
90 }
91 }
92
93 single_threaded(|| unsafe {
94 let fun_ptr = do_call::<F> as *const ();
95 let clean_ptr = do_cleanup as *const ();
96 let x = false;
97 let fun = std::mem::transmute::<
98 *const (),
99 Option<unsafe extern "C" fn(*mut std::ffi::c_void) -> *mut extendr_ffi::SEXPREC>,
100 >(fun_ptr);
101 let cleanfun = std::mem::transmute::<
102 *const (),
103 std::option::Option<unsafe extern "C" fn(*mut std::ffi::c_void, extendr_ffi::Rboolean)>,
104 >(clean_ptr);
105 let data = &f as *const _ as _;
106 let cleandata = &x as *const _ as _;
107 let cont = R_MakeUnwindCont();
108 Rf_protect(cont);
109
110 let res = match std::panic::catch_unwind(|| {
112 R_UnwindProtect(fun, data, cleanfun, cleandata, cont)
113 }) {
114 Ok(res) => Ok(res),
115 Err(_) => Err("Error in protected R code".into()),
116 };
117 Rf_unprotect(1);
118 res
119 })
120}
121
122#[no_mangle]
126pub extern "C" fn register_extendr_panic_hook() {
127 static RUN_ONCE: std::sync::Once = std::sync::Once::new();
128 RUN_ONCE.call_once_force(|x| {
129 if x.is_poisoned() {
131 println!("warning: extendr panic hook info registration was done more than once");
132 return;
133 }
134 let default_hook = std::panic::take_hook();
135 std::panic::set_hook(Box::new(move |x| {
136 let show_traceback = std::env::var("EXTENDR_BACKTRACE")
137 .map(|v| v.eq_ignore_ascii_case("true") || v == "1")
138 .unwrap_or(false);
139 if show_traceback {
140 default_hook(x)
141 } else {
142 return;
143 }
144 }));
145 });
146}