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) -> ! {
53 let s = s.as_ref();
54 unsafe {
55 R_ERROR_BUF = Some(crate::conditions::to_c_message(s));
56 let ptr = std::ptr::addr_of!(R_ERROR_BUF);
57 let msg = (*ptr).as_ref().map_or(c"".as_ptr(), |msg| msg.as_ptr());
58 Rf_error(c"%s".as_ptr(), msg);
59 };
60}
61
62pub fn stop<S: AsRef<str>>(s: S) -> ! {
64 throw_r_error(s)
65}
66
67pub fn catch_r_error<F>(f: F) -> Result<SEXP>
79where
80 F: FnOnce() -> SEXP + Copy,
81 F: std::panic::UnwindSafe,
82{
83 use std::os::raw;
84
85 unsafe extern "C" fn do_call<F>(data: *mut raw::c_void) -> SEXP
86 where
87 F: FnOnce() -> SEXP + Copy,
88 {
89 let data = data as *const ();
90 let f: &F = &*(data as *const F);
91 f()
92 }
93
94 unsafe extern "C" fn do_cleanup(_: *mut raw::c_void, jump: Rboolean) {
95 if jump != Rboolean::FALSE {
96 panic!("R has thrown an error.");
97 }
98 }
99
100 single_threaded(|| unsafe {
101 let fun_ptr = do_call::<F> as *const ();
102 let clean_ptr = do_cleanup as *const ();
103 let x = false;
104 let fun = std::mem::transmute::<
105 *const (),
106 Option<unsafe extern "C" fn(*mut std::ffi::c_void) -> *mut extendr_ffi::SEXPREC>,
107 >(fun_ptr);
108 let cleanfun = std::mem::transmute::<
109 *const (),
110 std::option::Option<unsafe extern "C" fn(*mut std::ffi::c_void, extendr_ffi::Rboolean)>,
111 >(clean_ptr);
112 let data = &f as *const _ as _;
113 let cleandata = &x as *const _ as _;
114 let cont = R_MakeUnwindCont();
115 Rf_protect(cont);
116
117 let res = match std::panic::catch_unwind(|| {
119 R_UnwindProtect(fun, data, cleanfun, cleandata, cont)
120 }) {
121 Ok(res) => Ok(res),
122 Err(_) => Err("Error in protected R code".into()),
123 };
124 Rf_unprotect(1);
125 res
126 })
127}
128
129#[no_mangle]
133pub extern "C" fn register_extendr_panic_hook() {
134 static RUN_ONCE: std::sync::Once = std::sync::Once::new();
135 RUN_ONCE.call_once_force(|x| {
136 if x.is_poisoned() {
138 println!("warning: extendr panic hook info registration was done more than once");
139 return;
140 }
141 let default_hook = std::panic::take_hook();
142 std::panic::set_hook(Box::new(move |x| {
143 let show_traceback = std::env::var("EXTENDR_BACKTRACE")
144 .map(|v| v.eq_ignore_ascii_case("true") || v == "1")
145 .unwrap_or(false);
146 if show_traceback {
147 default_hook(x)
148 }
149 }));
150 });
151}