extendr_api/
thread_safety.rs

1//! Provide limited protection for multithreaded access to the R API.
2use 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
9/// A global lock, that should represent the global lock on the R-API.
10/// It is not tied to an actual instance of R.
11static R_API_LOCK: Mutex<()> = Mutex::new(());
12
13thread_local! {
14    static THREAD_HAS_LOCK: Cell<bool> = Cell::new(false);
15}
16
17/// Run `f` while ensuring that `f` runs in a single-threaded manner.
18///
19/// This is intended for single-threaded access of the R's C-API.
20/// It is possible to have nested calls of `single_threaded` without deadlocking.
21///
22/// Note: This will fail badly if the called function `f` panics or calls `Rf_error`.
23pub 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    // acquire R-API lock
30    let _guard = if !has_lock {
31        Some(R_API_LOCK.lock().unwrap())
32    } else {
33        None
34    };
35
36    // this thread now has the lock
37    THREAD_HAS_LOCK.with(|x| x.set(true));
38
39    let result = f();
40
41    // release the R-API lock
42    if _guard.is_some() {
43        THREAD_HAS_LOCK.with(|x| x.set(false));
44    }
45
46    result
47}
48
49/// This function is used by the wrapper logic to catch
50/// panics on return.
51///
52#[doc(hidden)]
53pub fn handle_panic<F, R>(err_str: &str, f: F) -> R
54where
55    F: FnOnce() -> R,
56    F: std::panic::UnwindSafe,
57{
58    match std::panic::catch_unwind(f) {
59        Ok(res) => res,
60        Err(_) => {
61            let err_str = CString::new(err_str).unwrap();
62            unsafe { Rf_error(err_str.as_ptr()) }
63        }
64    }
65}
66
67static mut R_ERROR_BUF: Option<std::ffi::CString> = None;
68
69pub fn throw_r_error<S: AsRef<str>>(s: S) -> ! {
70    let s = s.as_ref();
71    unsafe {
72        R_ERROR_BUF = Some(std::ffi::CString::new(s).unwrap());
73        Rf_error(R_ERROR_BUF.as_ref().unwrap().as_ptr());
74    };
75}
76
77/// Wrap an R function such as `Rf_findFunction` and convert errors and panics into results.
78/// ```ignore
79/// use extendr_api::prelude::*;
80/// test! {
81///    let res = catch_r_error(|| unsafe {
82///        throw_r_error("bad things!");
83///        std::ptr::null_mut()
84///    });
85///    assert_eq!(res.is_ok(), false);
86/// }
87/// ```
88pub fn catch_r_error<F>(f: F) -> Result<SEXP>
89where
90    F: FnOnce() -> SEXP + Copy,
91    F: std::panic::UnwindSafe,
92{
93    use std::os::raw;
94
95    unsafe extern "C" fn do_call<F>(data: *mut raw::c_void) -> SEXP
96    where
97        F: FnOnce() -> SEXP + Copy,
98    {
99        let data = data as *const ();
100        let f: &F = &*(data as *const F);
101        f()
102    }
103
104    unsafe extern "C" fn do_cleanup(_: *mut raw::c_void, jump: Rboolean) {
105        if jump != Rboolean::FALSE {
106            panic!("R has thrown an error.");
107        }
108    }
109
110    single_threaded(|| unsafe {
111        let fun_ptr = do_call::<F> as *const ();
112        let clean_ptr = do_cleanup as *const ();
113        let x = false;
114        let fun = std::mem::transmute(fun_ptr);
115        let cleanfun = std::mem::transmute(clean_ptr);
116        let data = &f as *const _ as _;
117        let cleandata = &x as *const _ as _;
118        let cont = R_MakeUnwindCont();
119        Rf_protect(cont);
120
121        // Note that catch_unwind does not work for 32 bit windows targets.
122        let res = match std::panic::catch_unwind(|| {
123            R_UnwindProtect(fun, data, cleanfun, cleandata, cont)
124        }) {
125            Ok(res) => Ok(res),
126            Err(_) => Err("Error in protected R code".into()),
127        };
128        Rf_unprotect(1);
129        res
130    })
131}