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